Merge branch 'main' of https://github.com/frappe/lms into lms-frappe-ui
This commit is contained in:
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
@@ -78,4 +78,3 @@ jobs:
|
|||||||
- name: run tests
|
- name: run tests
|
||||||
working-directory: /home/runner/frappe-bench
|
working-directory: /home/runner/frappe-bench
|
||||||
run: bench --site frappe.local run-tests --app lms
|
run: bench --site frappe.local run-tests --app lms
|
||||||
|
|
||||||
|
|||||||
5
SECURITY.md
Normal file
5
SECURITY.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
The Frappe team and community take security issues seriously. To report a security issue, please go through the information mentioned [here](https://frappe.io/security).
|
||||||
|
|
||||||
|
We appreciate your efforts to responsibly disclose your findings. We'll endeavor to respond quickly and will keep you updated throughout the process.
|
||||||
@@ -13,6 +13,6 @@ module.exports = defineConfig({
|
|||||||
openMode: 0,
|
openMode: 0,
|
||||||
},
|
},
|
||||||
e2e: {
|
e2e: {
|
||||||
baseUrl: "http://dd1:8000",
|
baseUrl: "http://pyp:8000",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -33,19 +33,17 @@ describe("Course Creation", () => {
|
|||||||
cy.get("#lesson-title").type("Test Lesson");
|
cy.get("#lesson-title").type("Test Lesson");
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
cy.get(".ce-block").click().type("{enter}");
|
cy.get(".collapse-section.collapsed:first").click();
|
||||||
cy.get(".ce-toolbar__plus").click();
|
cy.get("#lesson-content .ce-block")
|
||||||
cy.get('[data-item-name="youtube"]').click();
|
.click()
|
||||||
|
.type(
|
||||||
|
"This is an extremely big paragraph that is meant to test the UI. This is a very long paragraph. It contains more than once sentence. Its meant to be this long as this is a UI test. Its unbearably long and I'm not sure why I'm typing this much. I'm just going to keep typing until I feel like its long enough. I think its long enough now. I'm going to stop typing now. {enter}"
|
||||||
|
);
|
||||||
|
cy.get("#lesson-content .ce-toolbar__plus").click();
|
||||||
|
cy.get('#lesson-content [data-item-name="youtube"]').click();
|
||||||
cy.get('input[data-fieldname="youtube"]').type("GoDtyItReto");
|
cy.get('input[data-fieldname="youtube"]').type("GoDtyItReto");
|
||||||
cy.button("Insert").click();
|
cy.button("Insert").click();
|
||||||
cy.wait(1000);
|
cy.wait(1000);
|
||||||
|
|
||||||
cy.get(".ce-block:last").click().type("{enter}");
|
|
||||||
cy.get(".ce-block:last")
|
|
||||||
.click()
|
|
||||||
.type(
|
|
||||||
"This is an extremely big paragraph that is meant to test the UI. This is a very long paragraph. It contains more than once sentence. Its meant to be this long as this is a UI test. Its unbearably long and I'm not sure why I'm typing this much. I'm just going to keep typing until I feel like its long enough. I think its long enough now. I'm going to stop typing now."
|
|
||||||
);
|
|
||||||
cy.button("Save").click();
|
cy.button("Save").click();
|
||||||
|
|
||||||
// View Course
|
// View Course
|
||||||
|
|||||||
@@ -138,12 +138,12 @@
|
|||||||
"label": "User Category",
|
"label": "User Category",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"mandatory_depends_on": null,
|
"mandatory_depends_on": null,
|
||||||
"modified": "2022-04-19 13:02:18.219508",
|
"modified": "2022-04-19 13:02:18.219510",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "User-user_category",
|
"name": "User-user_category",
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"non_negative": 0,
|
"non_negative": 0,
|
||||||
"options": "Business Owner\nManager (Sales/Marketing/Customer)\nEmployee\nStudent\nFreelancer/Just looking\nOthers",
|
"options": "\nBusiness Owner\nManager (Sales/Marketing/Customer)\nEmployee\nStudent\nFreelancer/Just looking\nOthers",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"precision": "",
|
"precision": "",
|
||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
|
|||||||
@@ -98,7 +98,6 @@ override_doctype_class = {
|
|||||||
|
|
||||||
doc_events = {
|
doc_events = {
|
||||||
"Discussion Reply": {"after_insert": "lms.lms.utils.create_notification_log"},
|
"Discussion Reply": {"after_insert": "lms.lms.utils.create_notification_log"},
|
||||||
"Course Lesson": {"on_update": "lms.lms.doctype.lms_quiz.lms_quiz.update_lesson_info"},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Scheduled Tasks
|
# Scheduled Tasks
|
||||||
@@ -304,6 +303,9 @@ lms_markdown_macro_renderers = {
|
|||||||
"YouTubeVideo": "lms.plugins.youtube_video_renderer",
|
"YouTubeVideo": "lms.plugins.youtube_video_renderer",
|
||||||
"Video": "lms.plugins.video_renderer",
|
"Video": "lms.plugins.video_renderer",
|
||||||
"Assignment": "lms.plugins.assignment_renderer",
|
"Assignment": "lms.plugins.assignment_renderer",
|
||||||
|
"Embed": "lms.plugins.embed_renderer",
|
||||||
|
"Audio": "lms.plugins.audio_renderer",
|
||||||
|
"PDF": "lms.plugins.pdf_renderer",
|
||||||
}
|
}
|
||||||
|
|
||||||
# page_renderer to manage profile pages
|
# page_renderer to manage profile pages
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ from frappe.desk.page.setup_wizard.setup_wizard import add_all_roles_to
|
|||||||
|
|
||||||
def after_install():
|
def after_install():
|
||||||
add_pages_to_nav()
|
add_pages_to_nav()
|
||||||
|
create_batch_source()
|
||||||
|
|
||||||
|
|
||||||
def after_sync():
|
def after_sync():
|
||||||
create_lms_roles()
|
create_lms_roles()
|
||||||
set_default_home()
|
|
||||||
set_default_certificate_print_format()
|
set_default_certificate_print_format()
|
||||||
add_all_roles_to("Administrator")
|
add_all_roles_to("Administrator")
|
||||||
|
|
||||||
@@ -54,6 +54,7 @@ def create_lms_roles():
|
|||||||
create_course_creator_role()
|
create_course_creator_role()
|
||||||
create_moderator_role()
|
create_moderator_role()
|
||||||
create_evaluator_role()
|
create_evaluator_role()
|
||||||
|
create_lms_student_role()
|
||||||
|
|
||||||
|
|
||||||
def delete_lms_roles():
|
def delete_lms_roles():
|
||||||
@@ -63,10 +64,6 @@ def delete_lms_roles():
|
|||||||
frappe.db.delete("Role", role)
|
frappe.db.delete("Role", role)
|
||||||
|
|
||||||
|
|
||||||
def set_default_home():
|
|
||||||
frappe.db.set_single_value("Portal Settings", "default_portal_home", "/courses")
|
|
||||||
|
|
||||||
|
|
||||||
def create_course_creator_role():
|
def create_course_creator_role():
|
||||||
if not frappe.db.exists("Role", "Course Creator"):
|
if not frappe.db.exists("Role", "Course Creator"):
|
||||||
role = frappe.get_doc(
|
role = frappe.get_doc(
|
||||||
@@ -106,6 +103,19 @@ def create_evaluator_role():
|
|||||||
role.save()
|
role.save()
|
||||||
|
|
||||||
|
|
||||||
|
def create_lms_student_role():
|
||||||
|
if not frappe.db.exists("Role", "LMS Student"):
|
||||||
|
role = frappe.new_doc("Role")
|
||||||
|
role.update(
|
||||||
|
{
|
||||||
|
"role_name": "LMS Student",
|
||||||
|
"home_page": "",
|
||||||
|
"desk_access": 0,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
role.save()
|
||||||
|
|
||||||
|
|
||||||
def set_default_certificate_print_format():
|
def set_default_certificate_print_format():
|
||||||
filters = {
|
filters = {
|
||||||
"doc_type": "LMS Certificate",
|
"doc_type": "LMS Certificate",
|
||||||
@@ -168,3 +178,20 @@ def delete_custom_fields():
|
|||||||
|
|
||||||
for field in fields:
|
for field in fields:
|
||||||
frappe.db.delete("Custom Field", {"fieldname": field})
|
frappe.db.delete("Custom Field", {"fieldname": field})
|
||||||
|
|
||||||
|
|
||||||
|
def create_batch_source():
|
||||||
|
sources = [
|
||||||
|
"Newsletter",
|
||||||
|
"LinkedIn",
|
||||||
|
"Twitter",
|
||||||
|
"Website",
|
||||||
|
"Friend/Colleague/Connection",
|
||||||
|
"Google Search",
|
||||||
|
]
|
||||||
|
|
||||||
|
for source in sources:
|
||||||
|
if not frappe.db.exists("LMS Source", source):
|
||||||
|
doc = frappe.new_doc("LMS Source")
|
||||||
|
doc.source = source
|
||||||
|
doc.save()
|
||||||
|
|||||||
@@ -116,7 +116,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"make_attachments_public": 1,
|
"make_attachments_public": 1,
|
||||||
"modified": "2022-09-15 17:22:21.662675",
|
"modified": "2023-09-29 17:03:30.825021",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Job",
|
"module": "Job",
|
||||||
"name": "Job Opportunity",
|
"name": "Job Opportunity",
|
||||||
@@ -144,7 +144,7 @@
|
|||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "All",
|
"role": "LMS Student",
|
||||||
"select": 1,
|
"select": 1,
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ def save_current_lesson(course_name, lesson_name):
|
|||||||
return
|
return
|
||||||
doc = frappe.get_doc("LMS Enrollment", name)
|
doc = frappe.get_doc("LMS Enrollment", name)
|
||||||
doc.current_lesson = lesson_name
|
doc.current_lesson = lesson_name
|
||||||
doc.save(ignore_permissions=True)
|
doc.save()
|
||||||
return {"current_lesson": doc.current_lesson}
|
return {"current_lesson": doc.current_lesson}
|
||||||
|
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ def join_cohort(course, cohort, subgroup, invite_code):
|
|||||||
return {"ok": True, "status": "record found"}
|
return {"ok": True, "status": "record found"}
|
||||||
else:
|
else:
|
||||||
doc = frappe.get_doc(data)
|
doc = frappe.get_doc(data)
|
||||||
doc.insert(ignore_permissions=True)
|
doc.insert()
|
||||||
return {"ok": True, "status": "record created"}
|
return {"ok": True, "status": "record created"}
|
||||||
|
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ def approve_cohort_join_request(join_request):
|
|||||||
return {"ok": False, "error": "Permission Deined"}
|
return {"ok": False, "error": "Permission Deined"}
|
||||||
|
|
||||||
r.status = "Accepted"
|
r.status = "Accepted"
|
||||||
r.save(ignore_permissions=True)
|
r.save()
|
||||||
return {"ok": True}
|
return {"ok": True}
|
||||||
|
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ def reject_cohort_join_request(join_request):
|
|||||||
return {"ok": False, "error": "Permission Deined"}
|
return {"ok": False, "error": "Permission Deined"}
|
||||||
|
|
||||||
r.status = "Rejected"
|
r.status = "Rejected"
|
||||||
r.save(ignore_permissions=True)
|
r.save()
|
||||||
return {"ok": True}
|
return {"ok": True}
|
||||||
|
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ def undo_reject_cohort_join_request(join_request):
|
|||||||
return {"ok": False, "error": "Permission Deined"}
|
return {"ok": False, "error": "Permission Deined"}
|
||||||
|
|
||||||
r.status = "Pending"
|
r.status = "Pending"
|
||||||
r.save(ignore_permissions=True)
|
r.save()
|
||||||
return {"ok": True}
|
return {"ok": True}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,12 @@
|
|||||||
"field_order": [
|
"field_order": [
|
||||||
"student_details_section",
|
"student_details_section",
|
||||||
"student",
|
"student",
|
||||||
"payment",
|
|
||||||
"column_break_oduu",
|
|
||||||
"student_name",
|
"student_name",
|
||||||
"username"
|
"username",
|
||||||
|
"column_break_oduu",
|
||||||
|
"payment",
|
||||||
|
"source",
|
||||||
|
"confirmation_email_sent"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -52,12 +54,24 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Payment",
|
"label": "Payment",
|
||||||
"options": "LMS Payment"
|
"options": "LMS Payment"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "confirmation_email_sent",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Confirmation Email Sent"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "source",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Source",
|
||||||
|
"options": "LMS Source"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-08-24 17:48:53.045539",
|
"modified": "2023-10-26 16:52:04.266693",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "Batch Student",
|
"name": "Batch Student",
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-12-16 15:06:03.985221",
|
"modified": "2023-09-29 17:08:18.950560",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "Cohort Join Request",
|
"name": "Cohort Join Request",
|
||||||
@@ -68,9 +68,21 @@
|
|||||||
"role": "System Manager",
|
"role": "System Manager",
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "LMS Student",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
"link_fieldname": "chapter"
|
"link_fieldname": "chapter"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2022-03-14 17:57:00.707416",
|
"modified": "2023-09-29 17:03:58.013819",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "Course Chapter",
|
"name": "Course Chapter",
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "All",
|
"role": "LMS Student",
|
||||||
"select": 1,
|
"select": 1,
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
"file_type",
|
"file_type",
|
||||||
"section_break_11",
|
"section_break_11",
|
||||||
"body",
|
"body",
|
||||||
|
"instructor_notes",
|
||||||
"help_section",
|
"help_section",
|
||||||
"help"
|
"help"
|
||||||
],
|
],
|
||||||
@@ -131,11 +132,16 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "column_break_15",
|
"fieldname": "column_break_15",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "instructor_notes",
|
||||||
|
"fieldtype": "Markdown Editor",
|
||||||
|
"label": "Instructor Notes"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-05-02 12:42:16.926753",
|
"modified": "2023-09-29 17:04:19.252897",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "Course Lesson",
|
"name": "Course Lesson",
|
||||||
@@ -163,7 +169,7 @@
|
|||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "All",
|
"role": "LMS Student",
|
||||||
"select": 1,
|
"select": 1,
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
|
|||||||
@@ -99,8 +99,14 @@ def save_progress(lesson, course, status):
|
|||||||
quizzes = [value for name, value in macros if name == "Quiz"]
|
quizzes = [value for name, value in macros if name == "Quiz"]
|
||||||
|
|
||||||
for quiz in quizzes:
|
for quiz in quizzes:
|
||||||
|
passing_percentage = frappe.db.get_value("LMS Quiz", quiz, "passing_percentage")
|
||||||
if not frappe.db.exists(
|
if not frappe.db.exists(
|
||||||
"LMS Quiz Submission", {"quiz": quiz, "owner": frappe.session.user}
|
"LMS Quiz Submission",
|
||||||
|
{
|
||||||
|
"quiz": quiz,
|
||||||
|
"owner": frappe.session.user,
|
||||||
|
"percentage": [">=", passing_percentage],
|
||||||
|
},
|
||||||
):
|
):
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-12-21 09:34:35.018280",
|
"modified": "2023-09-29 17:04:58.167481",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "Function",
|
"name": "Function",
|
||||||
@@ -44,11 +44,12 @@
|
|||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "All",
|
"role": "LMS Student",
|
||||||
"select": 1,
|
"select": 1,
|
||||||
"share": 1
|
"share": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC"
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-12-21 09:35:20.443192",
|
"modified": "2023-09-29 17:05:27.231982",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "Industry",
|
"name": "Industry",
|
||||||
@@ -44,11 +44,12 @@
|
|||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "All",
|
"role": "LMS Student",
|
||||||
"select": 1,
|
"select": 1,
|
||||||
"share": 1
|
"share": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC"
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
}
|
}
|
||||||
@@ -11,7 +11,11 @@ from frappe.utils.password import get_decrypted_password
|
|||||||
|
|
||||||
class InviteRequest(Document):
|
class InviteRequest(Document):
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
if self.has_value_changed("status") and self.status == "Approved":
|
if (
|
||||||
|
self.has_value_changed("status")
|
||||||
|
and self.status == "Approved"
|
||||||
|
and not frappe.flags.in_test
|
||||||
|
):
|
||||||
self.send_email()
|
self.send_email()
|
||||||
|
|
||||||
def create_user(self, password):
|
def create_user(self, password):
|
||||||
|
|||||||
@@ -9,10 +9,12 @@
|
|||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"title",
|
"title",
|
||||||
|
"grade_assignment",
|
||||||
|
"question",
|
||||||
"column_break_hmwv",
|
"column_break_hmwv",
|
||||||
"type",
|
"type",
|
||||||
"section_break_lwvt",
|
"show_answer",
|
||||||
"question"
|
"answer"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -26,7 +28,7 @@
|
|||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Type",
|
"label": "Type",
|
||||||
"options": "Document\nPDF\nURL\nImage"
|
"options": "Document\nPDF\nURL\nImage\nText"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "title",
|
"fieldname": "title",
|
||||||
@@ -40,13 +42,29 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_lwvt",
|
"default": "0",
|
||||||
"fieldtype": "Section Break"
|
"depends_on": "eval:doc.type == \"Text\"",
|
||||||
|
"fieldname": "show_answer",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Show Answer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "show_answer",
|
||||||
|
"fieldname": "answer",
|
||||||
|
"fieldtype": "Text Editor",
|
||||||
|
"label": "Answer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"depends_on": "eval:doc.type == \"Text\"",
|
||||||
|
"fieldname": "grade_assignment",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Grade Assignment"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-06-26 18:09:29.809564",
|
"modified": "2023-10-06 12:08:46.898950",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Assignment",
|
"name": "LMS Assignment",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from lms.lms.utils import can_create_courses
|
from lms.lms.utils import has_course_moderator_role, has_course_instructor_role
|
||||||
|
|
||||||
|
|
||||||
class LMSAssignment(Document):
|
class LMSAssignment(Document):
|
||||||
@@ -12,7 +12,7 @@ class LMSAssignment(Document):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def save_assignment(assignment, title, type, question):
|
def save_assignment(assignment, title, type, question):
|
||||||
if not can_create_courses():
|
if not has_course_moderator_role() or not has_course_instructor_role():
|
||||||
return
|
return
|
||||||
|
|
||||||
if assignment:
|
if assignment:
|
||||||
|
|||||||
@@ -2,6 +2,13 @@
|
|||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.ui.form.on("LMS Assignment Submission", {
|
frappe.ui.form.on("LMS Assignment Submission", {
|
||||||
// refresh: function(frm) {
|
onload: function (frm) {
|
||||||
// }
|
frm.set_query("member", function (doc) {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
ignore_user_type: 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,29 +9,29 @@
|
|||||||
"field_order": [
|
"field_order": [
|
||||||
"assignment",
|
"assignment",
|
||||||
"assignment_title",
|
"assignment_title",
|
||||||
"question",
|
"type",
|
||||||
"column_break_3",
|
"column_break_3",
|
||||||
"member",
|
"member",
|
||||||
"member_name",
|
"member_name",
|
||||||
"type",
|
"section_break_dlzh",
|
||||||
|
"question",
|
||||||
|
"column_break_zvis",
|
||||||
"assignment_attachment",
|
"assignment_attachment",
|
||||||
"answer",
|
"answer",
|
||||||
"section_break_rqal",
|
"section_break_rqal",
|
||||||
"status",
|
"status",
|
||||||
|
"evaluator",
|
||||||
"column_break_esgd",
|
"column_break_esgd",
|
||||||
"comments",
|
"comments",
|
||||||
"section_break_cwaw",
|
"section_break_cwaw",
|
||||||
"lesson",
|
"lesson",
|
||||||
"course",
|
"course",
|
||||||
"column_break_ygdu",
|
"column_break_ygdu"
|
||||||
"evaluator"
|
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldname": "lesson",
|
"fieldname": "lesson",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 1,
|
|
||||||
"label": "Lesson",
|
"label": "Lesson",
|
||||||
"options": "Course Lesson"
|
"options": "Course Lesson"
|
||||||
},
|
},
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
"fieldname": "status",
|
"fieldname": "status",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Status",
|
"label": "Status",
|
||||||
"options": "Pass\nFail\nNot Graded"
|
"options": "Pass\nFail\nNot Graded\nNot Applicable"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "comments",
|
"fieldname": "comments",
|
||||||
@@ -94,7 +94,7 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.type != \"URL\";",
|
"depends_on": "eval:!([\"URL\", \"Text\"]).includes(doc.type);",
|
||||||
"fieldname": "assignment_attachment",
|
"fieldname": "assignment_attachment",
|
||||||
"fieldtype": "Attach",
|
"fieldtype": "Attach",
|
||||||
"label": "Assignment Attachment",
|
"label": "Assignment Attachment",
|
||||||
@@ -104,8 +104,9 @@
|
|||||||
"fetch_from": "assignment.type",
|
"fetch_from": "assignment.type",
|
||||||
"fieldname": "type",
|
"fieldname": "type",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
"label": "Type",
|
"label": "Type",
|
||||||
"options": "Document\nPDF\nURL\nImage"
|
"options": "Document\nPDF\nURL\nImage\nText"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "assignment.question",
|
"fetch_from": "assignment.question",
|
||||||
@@ -137,17 +138,25 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.type == \"URL\";",
|
"depends_on": "eval:([\"URL\", \"Text\"]).includes(doc.type);",
|
||||||
"fieldname": "answer",
|
"fieldname": "answer",
|
||||||
"fieldtype": "Long Text",
|
"fieldtype": "Text Editor",
|
||||||
"label": "Answer",
|
"label": "Answer",
|
||||||
"mandatory_depends_on": "eval:doc.type == \"URL\";"
|
"mandatory_depends_on": "eval:doc.type == \"URL\";"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_dlzh",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_zvis",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"make_attachments_public": 1,
|
"make_attachments_public": 1,
|
||||||
"modified": "2023-08-30 12:09:03.332820",
|
"modified": "2023-10-06 15:14:55.984714",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Assignment Submission",
|
"name": "LMS Assignment Submission",
|
||||||
@@ -181,6 +190,10 @@
|
|||||||
{
|
{
|
||||||
"color": "Red",
|
"color": "Red",
|
||||||
"title": "Fail"
|
"title": "Fail"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "Blue",
|
||||||
|
"title": "Not Applicable"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"title_field": "assignment_title"
|
"title_field": "assignment_title"
|
||||||
|
|||||||
@@ -5,12 +5,17 @@ import frappe
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import validate_url
|
from frappe.utils import validate_url
|
||||||
|
from frappe.email.doctype.email_template.email_template import get_email_template
|
||||||
|
|
||||||
|
|
||||||
class LMSAssignmentSubmission(Document):
|
class LMSAssignmentSubmission(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_duplicates()
|
self.validate_duplicates()
|
||||||
|
|
||||||
|
def after_insert(self):
|
||||||
|
if not frappe.flags.in_test:
|
||||||
|
self.send_mail()
|
||||||
|
|
||||||
def validate_duplicates(self):
|
def validate_duplicates(self):
|
||||||
if frappe.db.exists(
|
if frappe.db.exists(
|
||||||
"LMS Assignment Submission",
|
"LMS Assignment Submission",
|
||||||
@@ -23,6 +28,35 @@ class LMSAssignmentSubmission(Document):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def send_mail(self):
|
||||||
|
subject = _("New Assignment Submission")
|
||||||
|
template = "assignment_submission"
|
||||||
|
custom_template = frappe.db.get_single_value(
|
||||||
|
"LMS Settings", "assignment_submission_template"
|
||||||
|
)
|
||||||
|
|
||||||
|
args = {
|
||||||
|
"member_name": self.member_name,
|
||||||
|
"assignment_name": self.assignment,
|
||||||
|
"assignment_title": self.assignment_title,
|
||||||
|
"submission_name": self.name,
|
||||||
|
}
|
||||||
|
|
||||||
|
moderators = frappe.get_all("Has Role", {"role": "Moderator"}, pluck="parent")
|
||||||
|
|
||||||
|
if custom_template:
|
||||||
|
email_template = get_email_template(custom_template, args)
|
||||||
|
subject = email_template.get("subject")
|
||||||
|
content = email_template.get("message")
|
||||||
|
frappe.sendmail(
|
||||||
|
recipients=moderators,
|
||||||
|
subject=subject,
|
||||||
|
template=template if not custom_template else None,
|
||||||
|
content=content if custom_template else None,
|
||||||
|
args=args,
|
||||||
|
header=[subject, "green"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def upload_assignment(
|
def upload_assignment(
|
||||||
@@ -37,9 +71,12 @@ def upload_assignment(
|
|||||||
if frappe.session.user == "Guest":
|
if frappe.session.user == "Guest":
|
||||||
return
|
return
|
||||||
|
|
||||||
assignment_type = frappe.db.get_value("LMS Assignment", assignment, "type")
|
assignment_details = frappe.db.get_value(
|
||||||
|
"LMS Assignment", assignment, ["type", "grade_assignment"], as_dict=1
|
||||||
|
)
|
||||||
|
assignment_type = assignment_details.type
|
||||||
|
|
||||||
if assignment_type == "URL" and not answer:
|
if assignment_type in ["URL", "Text"] and not answer:
|
||||||
frappe.throw(_("Please enter the URL for assignment submission."))
|
frappe.throw(_("Please enter the URL for assignment submission."))
|
||||||
|
|
||||||
if assignment_type == "File" and not assignment_attachment:
|
if assignment_type == "File" and not assignment_attachment:
|
||||||
@@ -64,7 +101,9 @@ def upload_assignment(
|
|||||||
doc.update(
|
doc.update(
|
||||||
{
|
{
|
||||||
"assignment_attachment": assignment_attachment,
|
"assignment_attachment": assignment_attachment,
|
||||||
"status": status,
|
"status": "Not Applicable"
|
||||||
|
if assignment_type == "Text" and not assignment_details.grade_assignment
|
||||||
|
else status,
|
||||||
"comments": comments,
|
"comments": comments,
|
||||||
"answer": answer,
|
"answer": answer,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,25 +10,116 @@ frappe.ui.form.on("LMS Batch", {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.set_query("reference_doctype", "timetable", function () {
|
||||||
|
let doctypes = ["Course Lesson", "LMS Quiz", "LMS Assignment"];
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
name: ["in", doctypes],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.set_query("reference_doctype", "timetable_legends", function () {
|
||||||
|
let doctypes = ["Course Lesson", "LMS Quiz", "LMS Assignment"];
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
name: ["in", doctypes],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
fetch_lessons: (frm) => {
|
timetable_template: function (frm) {
|
||||||
frm.clear_table("scheduled_flow");
|
set_timetable(frm);
|
||||||
frappe.call({
|
|
||||||
method: "lms.lms.doctype.lms_batch.lms_batch.fetch_lessons",
|
|
||||||
args: {
|
|
||||||
courses: frm.doc.courses,
|
|
||||||
},
|
|
||||||
callback: (r) => {
|
|
||||||
if (r.message) {
|
|
||||||
r.message.forEach((lesson) => {
|
|
||||||
let row = frm.add_child("scheduled_flow");
|
|
||||||
row.lesson = lesson.name;
|
|
||||||
row.lesson_title = lesson.title;
|
|
||||||
});
|
|
||||||
frm.refresh_field("scheduled_flow");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const set_timetable = (frm) => {
|
||||||
|
if (frm.doc.timetable_template) {
|
||||||
|
frm.clear_table("timetable");
|
||||||
|
frm.refresh_fields();
|
||||||
|
|
||||||
|
frappe.call({
|
||||||
|
method: "frappe.client.get_list",
|
||||||
|
args: {
|
||||||
|
doctype: "LMS Batch Timetable",
|
||||||
|
parent: "LMS Timetable Template",
|
||||||
|
fields: [
|
||||||
|
"reference_doctype",
|
||||||
|
"reference_docname",
|
||||||
|
"day",
|
||||||
|
"start_time",
|
||||||
|
"end_time",
|
||||||
|
"duration",
|
||||||
|
"milestone",
|
||||||
|
],
|
||||||
|
filters: {
|
||||||
|
parent: frm.doc.timetable_template,
|
||||||
|
parenttype: "LMS Timetable Template",
|
||||||
|
},
|
||||||
|
order_by: "idx",
|
||||||
|
},
|
||||||
|
callback: (data) => {
|
||||||
|
add_timetable_rows(frm, data.message);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const add_timetable_rows = (frm, timetable) => {
|
||||||
|
timetable.forEach((row) => {
|
||||||
|
let child = frm.add_child("timetable");
|
||||||
|
child.reference_doctype = row.reference_doctype;
|
||||||
|
child.reference_docname = row.reference_docname;
|
||||||
|
child.date = frappe.datetime.add_days(frm.doc.start_date, row.day - 1);
|
||||||
|
child.start_time = row.start_time;
|
||||||
|
child.end_time = row.end_time
|
||||||
|
? row.end_time
|
||||||
|
: row.duration
|
||||||
|
? moment
|
||||||
|
.utc(row.start_time, "HH:mm")
|
||||||
|
.add(row.duration, "hour")
|
||||||
|
.format("HH:mm")
|
||||||
|
: null;
|
||||||
|
child.duration = row.duration;
|
||||||
|
child.milestone = row.milestone;
|
||||||
|
});
|
||||||
|
frm.refresh_field("timetable");
|
||||||
|
|
||||||
|
set_legends(frm);
|
||||||
|
};
|
||||||
|
|
||||||
|
const set_legends = (frm) => {
|
||||||
|
if (frm.doc.timetable_template) {
|
||||||
|
frm.clear_table("timetable_legends");
|
||||||
|
frm.refresh_fields();
|
||||||
|
frappe.call({
|
||||||
|
method: "frappe.client.get_list",
|
||||||
|
args: {
|
||||||
|
doctype: "LMS Timetable Legend",
|
||||||
|
parent: "LMS Timetable Template",
|
||||||
|
fields: ["reference_doctype", "label", "color"],
|
||||||
|
filters: {
|
||||||
|
parent: frm.doc.timetable_template,
|
||||||
|
parenttype: "LMS Timetable Template",
|
||||||
|
},
|
||||||
|
order_by: "idx",
|
||||||
|
},
|
||||||
|
callback: (data) => {
|
||||||
|
add_legend_rows(frm, data.message);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const add_legend_rows = (frm, legends) => {
|
||||||
|
legends.forEach((row) => {
|
||||||
|
let child = frm.add_child("timetable_legends");
|
||||||
|
child.reference_doctype = row.reference_doctype;
|
||||||
|
child.label = row.label;
|
||||||
|
child.color = row.color;
|
||||||
|
});
|
||||||
|
frm.refresh_field("timetable_legends");
|
||||||
|
frm.save();
|
||||||
|
};
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"column_break_4",
|
"column_break_4",
|
||||||
"start_time",
|
"start_time",
|
||||||
"end_time",
|
"end_time",
|
||||||
|
"published",
|
||||||
"section_break_rgfj",
|
"section_break_rgfj",
|
||||||
"medium",
|
"medium",
|
||||||
"category",
|
"category",
|
||||||
@@ -21,21 +22,34 @@
|
|||||||
"seat_count",
|
"seat_count",
|
||||||
"section_break_6",
|
"section_break_6",
|
||||||
"description",
|
"description",
|
||||||
|
"batch_details_raw",
|
||||||
|
"column_break_hlqw",
|
||||||
"batch_details",
|
"batch_details",
|
||||||
|
"meta_image",
|
||||||
|
"section_break_jgji",
|
||||||
"students",
|
"students",
|
||||||
"courses",
|
"courses",
|
||||||
|
"assessment_tab",
|
||||||
|
"assessment",
|
||||||
|
"schedule_tab",
|
||||||
|
"timetable_template",
|
||||||
|
"column_break_anya",
|
||||||
|
"show_live_class",
|
||||||
|
"allow_future",
|
||||||
|
"section_break_ontp",
|
||||||
|
"timetable",
|
||||||
|
"timetable_legends",
|
||||||
|
"pricing_tab",
|
||||||
"section_break_gsac",
|
"section_break_gsac",
|
||||||
"paid_batch",
|
"paid_batch",
|
||||||
"column_break_iens",
|
"column_break_iens",
|
||||||
"amount",
|
"amount",
|
||||||
"currency",
|
"currency",
|
||||||
|
"customisations_tab",
|
||||||
"section_break_ubxi",
|
"section_break_ubxi",
|
||||||
"custom_component",
|
"custom_component",
|
||||||
"assessment_tab",
|
"column_break_pxgb",
|
||||||
"assessment",
|
"custom_script"
|
||||||
"schedule_tab",
|
|
||||||
"fetch_lessons",
|
|
||||||
"scheduled_flow"
|
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -86,10 +100,9 @@
|
|||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "The HTML code entered here will be displayed on the batch details page.",
|
|
||||||
"fieldname": "custom_component",
|
"fieldname": "custom_component",
|
||||||
"fieldtype": "Code",
|
"fieldtype": "Code",
|
||||||
"label": "Custom Component",
|
"label": "Custom HTML",
|
||||||
"options": "HTML"
|
"options": "HTML"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -142,33 +155,23 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "category",
|
"fieldname": "category",
|
||||||
"fieldtype": "Autocomplete",
|
"fieldtype": "Link",
|
||||||
"label": "Category"
|
"label": "Category",
|
||||||
},
|
"options": "LMS Category"
|
||||||
{
|
|
||||||
"fieldname": "scheduled_flow",
|
|
||||||
"fieldtype": "Table",
|
|
||||||
"label": "Scheduled Flow",
|
|
||||||
"options": "Scheduled Flow"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"description": "These customisations will work on the main batch page.",
|
||||||
"fieldname": "section_break_ubxi",
|
"fieldname": "section_break_ubxi",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "fetch_lessons",
|
|
||||||
"fieldtype": "Button",
|
|
||||||
"label": "Fetch Lessons"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "schedule_tab",
|
"fieldname": "schedule_tab",
|
||||||
"fieldtype": "Tab Break",
|
"fieldtype": "Tab Break",
|
||||||
"label": "Schedule"
|
"label": "Timetable"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_gsac",
|
"fieldname": "section_break_gsac",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break"
|
||||||
"label": "Pricing"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_iens",
|
"fieldname": "column_break_iens",
|
||||||
@@ -192,11 +195,93 @@
|
|||||||
"fieldtype": "Text Editor",
|
"fieldtype": "Text Editor",
|
||||||
"label": "Batch Details",
|
"label": "Batch Details",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "published",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Published"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "timetable",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Timetable",
|
||||||
|
"options": "LMS Batch Timetable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "timetable_template",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Timetable Template",
|
||||||
|
"options": "LMS Timetable Template"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_anya",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "show_live_class",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Show live class"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_ontp",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "batch_details_raw",
|
||||||
|
"fieldtype": "HTML Editor",
|
||||||
|
"label": "Batch Details Raw"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_hlqw",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_jgji",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "meta_image",
|
||||||
|
"fieldtype": "Attach Image",
|
||||||
|
"label": "Meta Image"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_pxgb",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "customisations_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Customisations"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "pricing_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Pricing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "custom_script",
|
||||||
|
"fieldtype": "Code",
|
||||||
|
"label": "Custom Script (JavaScript)",
|
||||||
|
"options": "Javascript"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "timetable_legends",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Timetable Legends",
|
||||||
|
"options": "LMS Timetable Legend"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"fieldname": "allow_future",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Allow accessing future dates"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-08-23 17:35:42.750754",
|
"modified": "2023-10-12 12:53:37.351989",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Batch",
|
"name": "LMS Batch",
|
||||||
|
|||||||
@@ -6,9 +6,17 @@ import requests
|
|||||||
import base64
|
import base64
|
||||||
import json
|
import json
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from datetime import timedelta
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import cint, format_date, format_datetime
|
from frappe.utils import (
|
||||||
from lms.lms.utils import get_lessons
|
cint,
|
||||||
|
format_date,
|
||||||
|
format_datetime,
|
||||||
|
get_time,
|
||||||
|
)
|
||||||
|
from lms.lms.utils import get_lessons, get_lesson_index, get_lesson_url
|
||||||
|
from lms.www.utils import get_quiz_details, get_assignment_details
|
||||||
|
from frappe.email.doctype.email_template.email_template import get_email_template
|
||||||
|
|
||||||
|
|
||||||
class LMSBatch(Document):
|
class LMSBatch(Document):
|
||||||
@@ -19,7 +27,8 @@ class LMSBatch(Document):
|
|||||||
self.validate_duplicate_students()
|
self.validate_duplicate_students()
|
||||||
self.validate_duplicate_assessments()
|
self.validate_duplicate_assessments()
|
||||||
self.validate_membership()
|
self.validate_membership()
|
||||||
self.validate_schedule()
|
self.validate_timetable()
|
||||||
|
self.send_confirmation_mail()
|
||||||
|
|
||||||
def validate_duplicate_students(self):
|
def validate_duplicate_students(self):
|
||||||
students = [row.student for row in self.students]
|
students = [row.student for row in self.students]
|
||||||
@@ -53,6 +62,43 @@ class LMSBatch(Document):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def send_confirmation_mail(self):
|
||||||
|
for student in self.students:
|
||||||
|
|
||||||
|
if not student.confirmation_email_sent:
|
||||||
|
self.send_mail(student)
|
||||||
|
student.confirmation_email_sent = 1
|
||||||
|
|
||||||
|
def send_mail(self, student):
|
||||||
|
subject = _("Enrollment Confirmation for the Next Training Batch")
|
||||||
|
template = "batch_confirmation"
|
||||||
|
custom_template = frappe.db.get_single_value(
|
||||||
|
"LMS Settings", "batch_confirmation_template"
|
||||||
|
)
|
||||||
|
|
||||||
|
args = {
|
||||||
|
"student_name": student.student_name,
|
||||||
|
"start_time": self.start_time,
|
||||||
|
"start_date": self.start_date,
|
||||||
|
"medium": self.medium,
|
||||||
|
"name": self.name,
|
||||||
|
}
|
||||||
|
|
||||||
|
if custom_template:
|
||||||
|
email_template = get_email_template(custom_template, args)
|
||||||
|
subject = email_template.get("subject")
|
||||||
|
content = email_template.get("message")
|
||||||
|
|
||||||
|
frappe.sendmail(
|
||||||
|
recipients=student.student,
|
||||||
|
subject=subject,
|
||||||
|
template=template if not custom_template else None,
|
||||||
|
content=content if custom_template else None,
|
||||||
|
args=args,
|
||||||
|
header=[subject, "green"],
|
||||||
|
retry=3,
|
||||||
|
)
|
||||||
|
|
||||||
def validate_membership(self):
|
def validate_membership(self):
|
||||||
for course in self.courses:
|
for course in self.courses:
|
||||||
for student in self.students:
|
for student in self.students:
|
||||||
@@ -68,26 +114,30 @@ class LMSBatch(Document):
|
|||||||
if cint(self.seat_count) < len(self.students):
|
if cint(self.seat_count) < len(self.students):
|
||||||
frappe.throw(_("There are no seats available in this batch."))
|
frappe.throw(_("There are no seats available in this batch."))
|
||||||
|
|
||||||
def validate_schedule(self):
|
def validate_timetable(self):
|
||||||
for schedule in self.scheduled_flow:
|
for schedule in self.timetable:
|
||||||
if schedule.start_time and schedule.end_time:
|
if schedule.start_time and schedule.end_time:
|
||||||
if (
|
if get_time(schedule.start_time) > get_time(schedule.end_time) or get_time(
|
||||||
schedule.start_time > schedule.end_time or schedule.start_time == schedule.end_time
|
schedule.start_time
|
||||||
):
|
) == get_time(schedule.end_time):
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("Row #{0} Start time cannot be greater than or equal to end time.").format(
|
_("Row #{0} Start time cannot be greater than or equal to end time.").format(
|
||||||
schedule.idx
|
schedule.idx
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if schedule.start_time < self.start_time or schedule.start_time > self.end_time:
|
if get_time(schedule.start_time) < get_time(self.start_time) or get_time(
|
||||||
|
schedule.start_time
|
||||||
|
) > get_time(self.end_time):
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("Row #{0} Start time cannot be outside the batch duration.").format(
|
_("Row #{0} Start time cannot be outside the batch duration.").format(
|
||||||
schedule.idx
|
schedule.idx
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if schedule.end_time < self.start_time or schedule.end_time > self.end_time:
|
if get_time(schedule.end_time) < get_time(self.start_time) or get_time(
|
||||||
|
schedule.end_time
|
||||||
|
) > get_time(self.end_time):
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("Row #{0} End time cannot be outside the batch duration.").format(schedule.idx)
|
_("Row #{0} End time cannot be outside the batch duration.").format(schedule.idx)
|
||||||
)
|
)
|
||||||
@@ -192,6 +242,8 @@ def create_batch(
|
|||||||
end_date,
|
end_date,
|
||||||
description=None,
|
description=None,
|
||||||
batch_details=None,
|
batch_details=None,
|
||||||
|
batch_details_raw=None,
|
||||||
|
meta_image=None,
|
||||||
seat_count=0,
|
seat_count=0,
|
||||||
start_time=None,
|
start_time=None,
|
||||||
end_time=None,
|
end_time=None,
|
||||||
@@ -201,6 +253,7 @@ def create_batch(
|
|||||||
amount=0,
|
amount=0,
|
||||||
currency=None,
|
currency=None,
|
||||||
name=None,
|
name=None,
|
||||||
|
published=0,
|
||||||
):
|
):
|
||||||
frappe.only_for("Moderator")
|
frappe.only_for("Moderator")
|
||||||
if name:
|
if name:
|
||||||
@@ -215,6 +268,8 @@ def create_batch(
|
|||||||
"end_date": end_date,
|
"end_date": end_date,
|
||||||
"description": description,
|
"description": description,
|
||||||
"batch_details": batch_details,
|
"batch_details": batch_details,
|
||||||
|
"batch_details_raw": batch_details_raw,
|
||||||
|
"image": meta_image,
|
||||||
"seat_count": seat_count,
|
"seat_count": seat_count,
|
||||||
"start_time": start_time,
|
"start_time": start_time,
|
||||||
"end_time": end_time,
|
"end_time": end_time,
|
||||||
@@ -223,6 +278,7 @@ def create_batch(
|
|||||||
"paid_batch": paid_batch,
|
"paid_batch": paid_batch,
|
||||||
"amount": amount,
|
"amount": amount,
|
||||||
"currency": currency,
|
"currency": currency,
|
||||||
|
"published": published,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
doc.save()
|
doc.save()
|
||||||
@@ -243,6 +299,10 @@ def fetch_lessons(courses):
|
|||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def add_course(course, parent, name=None, evaluator=None):
|
def add_course(course, parent, name=None, evaluator=None):
|
||||||
frappe.only_for("Moderator")
|
frappe.only_for("Moderator")
|
||||||
|
|
||||||
|
if frappe.db.exists("Batch Course", {"course": course, "parent": parent}):
|
||||||
|
frappe.throw(_("Course already added to the batch."))
|
||||||
|
|
||||||
if name:
|
if name:
|
||||||
doc = frappe.get_doc("Batch Course", name)
|
doc = frappe.get_doc("Batch Course", name)
|
||||||
else:
|
else:
|
||||||
@@ -260,3 +320,119 @@ def add_course(course, parent, name=None, evaluator=None):
|
|||||||
doc.save()
|
doc.save()
|
||||||
|
|
||||||
return doc.name
|
return doc.name
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_batch_timetable(batch):
|
||||||
|
timetable = frappe.get_all(
|
||||||
|
"LMS Batch Timetable",
|
||||||
|
filters={"parent": batch},
|
||||||
|
fields=[
|
||||||
|
"reference_doctype",
|
||||||
|
"reference_docname",
|
||||||
|
"date",
|
||||||
|
"start_time",
|
||||||
|
"end_time",
|
||||||
|
"milestone",
|
||||||
|
"name",
|
||||||
|
"idx",
|
||||||
|
"parent",
|
||||||
|
],
|
||||||
|
order_by="date",
|
||||||
|
)
|
||||||
|
|
||||||
|
show_live_class = frappe.db.get_value("LMS Batch", batch, "show_live_class")
|
||||||
|
if show_live_class:
|
||||||
|
live_classes = get_live_classes(batch)
|
||||||
|
timetable.extend(live_classes)
|
||||||
|
|
||||||
|
timetable = get_timetable_details(timetable)
|
||||||
|
return timetable
|
||||||
|
|
||||||
|
|
||||||
|
def get_live_classes(batch):
|
||||||
|
live_classes = frappe.get_all(
|
||||||
|
"LMS Live Class",
|
||||||
|
{"batch_name": batch},
|
||||||
|
["name", "title", "date", "time as start_time", "duration", "join_url as url"],
|
||||||
|
order_by="date",
|
||||||
|
)
|
||||||
|
for class_ in live_classes:
|
||||||
|
class_.end_time = class_.start_time + timedelta(minutes=class_.duration)
|
||||||
|
class_.reference_doctype = "LMS Live Class"
|
||||||
|
class_.reference_docname = class_.name
|
||||||
|
class_.icon = "icon-call"
|
||||||
|
|
||||||
|
return live_classes
|
||||||
|
|
||||||
|
|
||||||
|
def get_timetable_details(timetable):
|
||||||
|
for entry in timetable:
|
||||||
|
entry.title = frappe.db.get_value(
|
||||||
|
entry.reference_doctype, entry.reference_docname, "title"
|
||||||
|
)
|
||||||
|
assessment = frappe._dict({"assessment_name": entry.reference_docname})
|
||||||
|
|
||||||
|
if entry.reference_doctype == "Course Lesson":
|
||||||
|
course = frappe.db.get_value(
|
||||||
|
entry.reference_doctype, entry.reference_docname, "course"
|
||||||
|
)
|
||||||
|
entry.url = get_lesson_url(course, get_lesson_index(entry.reference_docname))
|
||||||
|
|
||||||
|
entry.completed = (
|
||||||
|
True
|
||||||
|
if frappe.db.exists(
|
||||||
|
"LMS Course Progress",
|
||||||
|
{"lesson": entry.reference_docname, "member": frappe.session.user},
|
||||||
|
)
|
||||||
|
else False
|
||||||
|
)
|
||||||
|
|
||||||
|
elif entry.reference_doctype == "LMS Quiz":
|
||||||
|
entry.url = "/quizzes"
|
||||||
|
details = get_quiz_details(assessment, frappe.session.user)
|
||||||
|
entry.update(details)
|
||||||
|
|
||||||
|
elif entry.reference_doctype == "LMS Assignment":
|
||||||
|
details = get_assignment_details(assessment, frappe.session.user)
|
||||||
|
entry.update(details)
|
||||||
|
|
||||||
|
timetable = sorted(timetable, key=lambda k: k["date"])
|
||||||
|
return timetable
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def is_milestone_complete(idx, batch):
|
||||||
|
previous_rows = frappe.get_all(
|
||||||
|
"LMS Batch Timetable",
|
||||||
|
filters={"parent": batch, "idx": ["<", cint(idx)]},
|
||||||
|
fields=["reference_doctype", "reference_docname", "idx"],
|
||||||
|
order_by="idx",
|
||||||
|
)
|
||||||
|
|
||||||
|
for row in previous_rows:
|
||||||
|
if row.reference_doctype == "Course Lesson":
|
||||||
|
if not frappe.db.exists(
|
||||||
|
"LMS Course Progress",
|
||||||
|
{"member": frappe.session.user, "lesson": row.reference_docname},
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if row.reference_doctype == "LMS Quiz":
|
||||||
|
passing_percentage = frappe.db.get_value(
|
||||||
|
row.reference_doctype, row.reference_docname, "passing_percentage"
|
||||||
|
)
|
||||||
|
if not frappe.db.exists(
|
||||||
|
"LMS Quiz Submission",
|
||||||
|
{"quiz": row.reference_docname, "member": frappe.session.user},
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if row.reference_doctype == "LMS Assignment":
|
||||||
|
if not frappe.db.exists(
|
||||||
|
"LMS Assignment Submission",
|
||||||
|
{"assignment": row.reference_docname, "member": frappe.session.user},
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2023, Frappe and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("LMS Batch Timetable", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
93
lms/lms/doctype/lms_batch_timetable/lms_batch_timetable.json
Normal file
93
lms/lms/doctype/lms_batch_timetable/lms_batch_timetable.json
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"autoname": "hash",
|
||||||
|
"creation": "2023-09-14 12:44:51.098956",
|
||||||
|
"default_view": "List",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"column_break_htdc",
|
||||||
|
"reference_doctype",
|
||||||
|
"reference_docname",
|
||||||
|
"date",
|
||||||
|
"day",
|
||||||
|
"column_break_merq",
|
||||||
|
"start_time",
|
||||||
|
"end_time",
|
||||||
|
"duration",
|
||||||
|
"milestone"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "reference_doctype",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Reference DocType",
|
||||||
|
"options": "DocType"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "reference_docname",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Reference DocName",
|
||||||
|
"options": "reference_doctype"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_merq",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.parenttype == \"LMS Batch\";",
|
||||||
|
"fieldname": "date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "start_time",
|
||||||
|
"fieldtype": "Time",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Start Time"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "duration",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Duration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_htdc",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "end_time",
|
||||||
|
"fieldtype": "Time",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "End Time"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.parenttype == \"LMS Timetable Template\";",
|
||||||
|
"fieldname": "day",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Day"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "milestone",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Milestone"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2023-10-20 11:58:01.782921",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "LMS",
|
||||||
|
"name": "LMS Batch Timetable",
|
||||||
|
"naming_rule": "Random",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2023, Frappe and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class LMSBatchTimetable(Document):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2023, Frappe and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestLMSBatchTimetable(FrappeTestCase):
|
||||||
|
pass
|
||||||
@@ -10,6 +10,14 @@ frappe.ui.form.on("LMS Certificate", {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.set_query("template", function (doc) {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
doc_type: "LMS Certificate",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
},
|
},
|
||||||
refresh: (frm) => {
|
refresh: (frm) => {
|
||||||
if (frm.doc.name)
|
if (frm.doc.name)
|
||||||
|
|||||||
@@ -8,10 +8,12 @@
|
|||||||
"course",
|
"course",
|
||||||
"member",
|
"member",
|
||||||
"member_name",
|
"member_name",
|
||||||
|
"template",
|
||||||
"column_break_3",
|
"column_break_3",
|
||||||
"issue_date",
|
"issue_date",
|
||||||
"expiry_date",
|
"expiry_date",
|
||||||
"batch_name"
|
"batch_name",
|
||||||
|
"published"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -60,11 +62,24 @@
|
|||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Batch",
|
"label": "Batch",
|
||||||
"options": "LMS Batch"
|
"options": "LMS Batch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "published",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Publish on Participant Page"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "template",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Template",
|
||||||
|
"options": "Print Format",
|
||||||
|
"reqd": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-08-29 15:23:08.637215",
|
"modified": "2023-10-25 12:20:56.091979",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Certificate",
|
"name": "LMS Certificate",
|
||||||
|
|||||||
@@ -6,12 +6,42 @@ from frappe import _
|
|||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import add_years, nowdate
|
from frappe.utils import add_years, nowdate
|
||||||
from lms.lms.utils import is_certified
|
from lms.lms.utils import is_certified
|
||||||
|
from frappe.email.doctype.email_template.email_template import get_email_template
|
||||||
|
|
||||||
|
|
||||||
class LMSCertificate(Document):
|
class LMSCertificate(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_duplicate_certificate()
|
self.validate_duplicate_certificate()
|
||||||
|
|
||||||
|
def after_insert(self):
|
||||||
|
if not frappe.flags.in_test:
|
||||||
|
self.send_mail()
|
||||||
|
|
||||||
|
def send_mail(self):
|
||||||
|
subject = _("Congratulations on getting certified!")
|
||||||
|
template = "certification"
|
||||||
|
custom_template = frappe.db.get_single_value("LMS Settings", "certification_template")
|
||||||
|
|
||||||
|
args = {
|
||||||
|
"student_name": self.member_name,
|
||||||
|
"course_name": self.course,
|
||||||
|
"course_title": frappe.db.get_value("LMS Course", self.course, "title"),
|
||||||
|
"certificate_name": self.name,
|
||||||
|
}
|
||||||
|
|
||||||
|
if custom_template:
|
||||||
|
email_template = get_email_template(custom_template, args)
|
||||||
|
subject = email_template.get("subject")
|
||||||
|
content = email_template.get("message")
|
||||||
|
frappe.sendmail(
|
||||||
|
recipients=self.member,
|
||||||
|
subject=subject,
|
||||||
|
template=template if not custom_template else None,
|
||||||
|
content=content if custom_template else None,
|
||||||
|
args=args,
|
||||||
|
header=[subject, "green"],
|
||||||
|
)
|
||||||
|
|
||||||
def validate_duplicate_certificate(self):
|
def validate_duplicate_certificate(self):
|
||||||
certificates = frappe.get_all(
|
certificates = frappe.get_all(
|
||||||
"LMS Certificate",
|
"LMS Certificate",
|
||||||
@@ -48,6 +78,15 @@ def create_certificate(course):
|
|||||||
if expires_after_yrs:
|
if expires_after_yrs:
|
||||||
expiry_date = add_years(nowdate(), expires_after_yrs)
|
expiry_date = add_years(nowdate(), expires_after_yrs)
|
||||||
|
|
||||||
|
default_certificate_template = frappe.db.get_value(
|
||||||
|
"Property Setter",
|
||||||
|
{
|
||||||
|
"doc_type": "LMS Certificate",
|
||||||
|
"property": "default_print_format",
|
||||||
|
},
|
||||||
|
"value",
|
||||||
|
)
|
||||||
|
|
||||||
certificate = frappe.get_doc(
|
certificate = frappe.get_doc(
|
||||||
{
|
{
|
||||||
"doctype": "LMS Certificate",
|
"doctype": "LMS Certificate",
|
||||||
@@ -55,6 +94,7 @@ def create_certificate(course):
|
|||||||
"course": course,
|
"course": course,
|
||||||
"issue_date": nowdate(),
|
"issue_date": nowdate(),
|
||||||
"expiry_date": expiry_date,
|
"expiry_date": expiry_date,
|
||||||
|
"template": default_certificate_template,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
certificate.save(ignore_permissions=True)
|
certificate.save(ignore_permissions=True)
|
||||||
|
|||||||
@@ -84,7 +84,7 @@
|
|||||||
"fieldname": "status",
|
"fieldname": "status",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Status",
|
"label": "Status",
|
||||||
"options": "Pass\nFail",
|
"options": "Pending\nIn Progress\nPass\nFail",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -106,7 +106,7 @@
|
|||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-08-23 14:51:21.947169",
|
"modified": "2023-09-26 19:44:43.594892",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Certificate Evaluation",
|
"name": "LMS Certificate Evaluation",
|
||||||
@@ -139,6 +139,23 @@
|
|||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": [
|
||||||
|
{
|
||||||
|
"color": "Green",
|
||||||
|
"title": "Pass"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "Red",
|
||||||
|
"title": "Fail"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "Blue",
|
||||||
|
"title": "Pending"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "Orange",
|
||||||
|
"title": "In Progress"
|
||||||
|
}
|
||||||
|
],
|
||||||
"title_field": "member_name"
|
"title_field": "member_name"
|
||||||
}
|
}
|
||||||
@@ -20,7 +20,6 @@ class LMSCourse(Document):
|
|||||||
self.image = validate_image(self.image)
|
self.image = validate_image(self.image)
|
||||||
|
|
||||||
def validate_instructors(self):
|
def validate_instructors(self):
|
||||||
print(self.is_new(), not self.instructors)
|
|
||||||
if self.is_new() and not self.instructors:
|
if self.is_new() and not self.instructors:
|
||||||
frappe.get_doc(
|
frappe.get_doc(
|
||||||
{
|
{
|
||||||
@@ -217,7 +216,7 @@ def save_course(
|
|||||||
course_price=None,
|
course_price=None,
|
||||||
currency=None,
|
currency=None,
|
||||||
):
|
):
|
||||||
if not can_create_courses():
|
if not can_create_courses(course):
|
||||||
return
|
return
|
||||||
|
|
||||||
if course:
|
if course:
|
||||||
@@ -281,6 +280,7 @@ def save_lesson(
|
|||||||
preview,
|
preview,
|
||||||
idx,
|
idx,
|
||||||
lesson,
|
lesson,
|
||||||
|
instructor_notes=None,
|
||||||
youtube=None,
|
youtube=None,
|
||||||
quiz_id=None,
|
quiz_id=None,
|
||||||
question=None,
|
question=None,
|
||||||
@@ -296,6 +296,7 @@ def save_lesson(
|
|||||||
"chapter": chapter,
|
"chapter": chapter,
|
||||||
"title": title,
|
"title": title,
|
||||||
"body": body,
|
"body": body,
|
||||||
|
"instructor_notes": instructor_notes,
|
||||||
"include_in_preview": preview,
|
"include_in_preview": preview,
|
||||||
"youtube": youtube,
|
"youtube": youtube,
|
||||||
"quiz_id": quiz_id,
|
"quiz_id": quiz_id,
|
||||||
|
|||||||
@@ -123,7 +123,7 @@
|
|||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-08-24 17:52:35.487141",
|
"modified": "2023-10-02 12:41:25.139734",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Enrollment",
|
"name": "LMS Enrollment",
|
||||||
@@ -140,6 +140,31 @@
|
|||||||
"role": "System Manager",
|
"role": "System Manager",
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "LMS Student",
|
||||||
|
"select": 1,
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Moderator",
|
||||||
|
"select": 1,
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
|
|||||||
@@ -10,16 +10,16 @@
|
|||||||
"title",
|
"title",
|
||||||
"host",
|
"host",
|
||||||
"batch_name",
|
"batch_name",
|
||||||
"password",
|
|
||||||
"auto_recording",
|
|
||||||
"column_break_astv",
|
"column_break_astv",
|
||||||
"description",
|
|
||||||
"section_break_glxh",
|
|
||||||
"date",
|
"date",
|
||||||
"timezone",
|
|
||||||
"column_break_spvt",
|
|
||||||
"time",
|
"time",
|
||||||
"duration",
|
"duration",
|
||||||
|
"section_break_glxh",
|
||||||
|
"description",
|
||||||
|
"column_break_spvt",
|
||||||
|
"timezone",
|
||||||
|
"password",
|
||||||
|
"auto_recording",
|
||||||
"section_break_yrpq",
|
"section_break_yrpq",
|
||||||
"start_url",
|
"start_url",
|
||||||
"column_break_yokr",
|
"column_break_yokr",
|
||||||
@@ -126,7 +126,7 @@
|
|||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-03-14 18:44:48.813103",
|
"modified": "2023-09-20 11:29:20.899897",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Live Class",
|
"name": "LMS Live Class",
|
||||||
@@ -157,8 +157,10 @@
|
|||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"show_title_field_in_link": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": [],
|
||||||
|
"title_field": "title",
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
@@ -8,13 +8,17 @@
|
|||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
|
"payment_for_document_type",
|
||||||
"member",
|
"member",
|
||||||
|
"source",
|
||||||
"column_break_rqkd",
|
"column_break_rqkd",
|
||||||
|
"payment_for_document",
|
||||||
"billing_name",
|
"billing_name",
|
||||||
"payment_received",
|
"payment_received",
|
||||||
"payment_details_section",
|
"payment_details_section",
|
||||||
"amount",
|
|
||||||
"currency",
|
"currency",
|
||||||
|
"amount",
|
||||||
|
"amount_with_gst",
|
||||||
"column_break_yxpl",
|
"column_break_yxpl",
|
||||||
"order_id",
|
"order_id",
|
||||||
"payment_id",
|
"payment_id",
|
||||||
@@ -39,7 +43,8 @@
|
|||||||
"fieldname": "amount",
|
"fieldname": "amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Amount"
|
"label": "Amount",
|
||||||
|
"options": "currency"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "currency",
|
"fieldname": "currency",
|
||||||
@@ -107,11 +112,35 @@
|
|||||||
"label": "Member",
|
"label": "Member",
|
||||||
"options": "User",
|
"options": "User",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.currency == \"INR\";",
|
||||||
|
"fieldname": "amount_with_gst",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Amount with GST"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "payment_for_document_type",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Payment for Document Type",
|
||||||
|
"options": "\nLMS Course\nLMS Batch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "payment_for_document",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"label": "Payment for Document",
|
||||||
|
"options": "payment_for_document_type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "source",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Source",
|
||||||
|
"options": "LMS Source"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-08-24 22:08:12.294960",
|
"modified": "2023-10-26 16:54:12.408274",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Payment",
|
"name": "LMS Payment",
|
||||||
|
|||||||
0
lms/lms/doctype/lms_question/__init__.py
Normal file
0
lms/lms/doctype/lms_question/__init__.py
Normal file
8
lms/lms/doctype/lms_question/lms_question.js
Normal file
8
lms/lms/doctype/lms_question/lms_question.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2023, Frappe and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("LMS Question", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
245
lms/lms/doctype/lms_question/lms_question.json
Normal file
245
lms/lms/doctype/lms_question/lms_question.json
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"autoname": "format:QTS-{YYYY}-{#####}",
|
||||||
|
"creation": "2023-10-10 10:24:14.035772",
|
||||||
|
"default_view": "List",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"question",
|
||||||
|
"type",
|
||||||
|
"multiple",
|
||||||
|
"section_break_ytxi",
|
||||||
|
"option_1",
|
||||||
|
"is_correct_1",
|
||||||
|
"column_break_fpvl",
|
||||||
|
"explanation_1",
|
||||||
|
"section_break_eiaa",
|
||||||
|
"option_2",
|
||||||
|
"is_correct_2",
|
||||||
|
"column_break_akwy",
|
||||||
|
"explanation_2",
|
||||||
|
"section_break_cwqv",
|
||||||
|
"option_3",
|
||||||
|
"is_correct_3",
|
||||||
|
"column_break_atpl",
|
||||||
|
"explanation_3",
|
||||||
|
"section_break_yqel",
|
||||||
|
"option_4",
|
||||||
|
"is_correct_4",
|
||||||
|
"column_break_lknb",
|
||||||
|
"explanation_4",
|
||||||
|
"section_break_hkfe",
|
||||||
|
"possibility_1",
|
||||||
|
"possibility_3",
|
||||||
|
"column_break_wpjr",
|
||||||
|
"possibility_2",
|
||||||
|
"possibility_4"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "question",
|
||||||
|
"fieldtype": "Text Editor",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Question"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "type",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Type",
|
||||||
|
"options": "Choices\nUser Input"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.type == \"Choices\";",
|
||||||
|
"fieldname": "section_break_ytxi",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "option_1",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Option 1",
|
||||||
|
"mandatory_depends_on": "eval: doc.type == 'Choices'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "is_correct_1",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Is Correct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_fpvl",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "explanation_1",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Explanation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.type == \"Choices\";",
|
||||||
|
"fieldname": "section_break_eiaa",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "option_2",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Option 2",
|
||||||
|
"mandatory_depends_on": "eval: doc.type == 'Choices'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "is_correct_2",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Is Correct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_akwy",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "explanation_2",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Explanation "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.type == 'Choices'",
|
||||||
|
"fieldname": "section_break_cwqv",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "option_3",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Option 3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "is_correct_3",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Is Correct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_atpl",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "explanation_3",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Explanation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.type == 'Choices'",
|
||||||
|
"fieldname": "section_break_yqel",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "option_4",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Option 4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "is_correct_4",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Is Correct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_lknb",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "explanation_4",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Explanation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "multiple",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Multiple Correct Answers"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.type == 'User Input'",
|
||||||
|
"fieldname": "section_break_hkfe",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_wpjr",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "possibility_1",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Possible Answer 1",
|
||||||
|
"mandatory_depends_on": "eval: doc.type == 'User Input'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "possibility_3",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Possible Answer 3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "possibility_2",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Possible Answer 2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "possibility_4",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Possible Answer 4"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2023-10-18 21:58:42.653317",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "LMS",
|
||||||
|
"name": "LMS Question",
|
||||||
|
"naming_rule": "Expression",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Moderator",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Course Creator",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
|
"title_field": "question"
|
||||||
|
}
|
||||||
92
lms/lms/doctype/lms_question/lms_question.py
Normal file
92
lms/lms/doctype/lms_question/lms_question.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
# Copyright (c) 2023, Frappe and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from frappe.model.document import Document
|
||||||
|
from lms.lms.utils import has_course_instructor_role, has_course_moderator_role
|
||||||
|
|
||||||
|
|
||||||
|
class LMSQuestion(Document):
|
||||||
|
def validate(self):
|
||||||
|
validate_correct_answers(self)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_correct_answers(question):
|
||||||
|
if question.type == "Choices":
|
||||||
|
validate_duplicate_options(question)
|
||||||
|
validate_correct_options(question)
|
||||||
|
else:
|
||||||
|
validate_possible_answer(question)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_duplicate_options(question):
|
||||||
|
options = []
|
||||||
|
|
||||||
|
for num in range(1, 5):
|
||||||
|
if question.get(f"option_{num}"):
|
||||||
|
options.append(question.get(f"option_{num}"))
|
||||||
|
|
||||||
|
if len(set(options)) != len(options):
|
||||||
|
frappe.throw(_("Duplicate options found for this question."))
|
||||||
|
|
||||||
|
|
||||||
|
def validate_correct_options(question):
|
||||||
|
correct_options = get_correct_options(question)
|
||||||
|
|
||||||
|
if len(correct_options) > 1:
|
||||||
|
question.multiple = 1
|
||||||
|
|
||||||
|
if not len(correct_options):
|
||||||
|
frappe.throw(_("At least one option must be correct for this question."))
|
||||||
|
|
||||||
|
|
||||||
|
def validate_possible_answer(question):
|
||||||
|
possible_answers = []
|
||||||
|
possible_answers_fields = [
|
||||||
|
"possibility_1",
|
||||||
|
"possibility_2",
|
||||||
|
"possibility_3",
|
||||||
|
"possibility_4",
|
||||||
|
]
|
||||||
|
|
||||||
|
for field in possible_answers_fields:
|
||||||
|
if question.get(field):
|
||||||
|
possible_answers.append(field)
|
||||||
|
|
||||||
|
if not len(possible_answers):
|
||||||
|
frappe.throw(
|
||||||
|
_("Add at least one possible answer for this question: {0}").format(
|
||||||
|
frappe.bold(question.question)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_correct_options(question):
|
||||||
|
correct_options = []
|
||||||
|
correct_option_fields = [
|
||||||
|
"is_correct_1",
|
||||||
|
"is_correct_2",
|
||||||
|
"is_correct_3",
|
||||||
|
"is_correct_4",
|
||||||
|
]
|
||||||
|
for field in correct_option_fields:
|
||||||
|
if question.get(field) == 1:
|
||||||
|
correct_options.append(field)
|
||||||
|
|
||||||
|
return correct_options
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_question_details(question):
|
||||||
|
if not has_course_instructor_role() or not has_course_moderator_role():
|
||||||
|
return
|
||||||
|
|
||||||
|
fields = ["question", "type", "name"]
|
||||||
|
for i in range(1, 5):
|
||||||
|
fields.append(f"option_{i}")
|
||||||
|
fields.append(f"is_correct_{i}")
|
||||||
|
fields.append(f"explanation_{i}")
|
||||||
|
fields.append(f"possibility_{i}")
|
||||||
|
|
||||||
|
return frappe.db.get_value("LMS Question", question, fields, as_dict=1)
|
||||||
9
lms/lms/doctype/lms_question/test_lms_question.py
Normal file
9
lms/lms/doctype/lms_question/test_lms_question.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2023, Frappe and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestLMSQuestion(FrappeTestCase):
|
||||||
|
pass
|
||||||
@@ -5,3 +5,13 @@ frappe.ui.form.on("LMS Quiz", {
|
|||||||
// refresh: function(frm) {
|
// refresh: function(frm) {
|
||||||
// }
|
// }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frappe.ui.form.on("LMS Quiz Question", {
|
||||||
|
marks: function (frm) {
|
||||||
|
total_marks = 0;
|
||||||
|
frm.doc.questions.forEach((question) => {
|
||||||
|
total_marks += question.marks;
|
||||||
|
});
|
||||||
|
frm.doc.total_marks = total_marks;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -12,6 +12,10 @@
|
|||||||
"column_break_gaac",
|
"column_break_gaac",
|
||||||
"max_attempts",
|
"max_attempts",
|
||||||
"show_submission_history",
|
"show_submission_history",
|
||||||
|
"section_break_hsiv",
|
||||||
|
"passing_percentage",
|
||||||
|
"column_break_rocd",
|
||||||
|
"total_marks",
|
||||||
"section_break_sbjx",
|
"section_break_sbjx",
|
||||||
"questions",
|
"questions",
|
||||||
"section_break_3",
|
"section_break_3",
|
||||||
@@ -43,7 +47,7 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "1",
|
"default": "0",
|
||||||
"fieldname": "max_attempts",
|
"fieldname": "max_attempts",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"label": "Max Attempts"
|
"label": "Max Attempts"
|
||||||
@@ -90,11 +94,35 @@
|
|||||||
"fieldname": "show_submission_history",
|
"fieldname": "show_submission_history",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Show Submission History"
|
"label": "Show Submission History"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_hsiv",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "passing_percentage",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Passing Percentage",
|
||||||
|
"non_negative": 1,
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_rocd",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "total_marks",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Total Marks",
|
||||||
|
"non_negative": 1,
|
||||||
|
"read_only": 1,
|
||||||
|
"reqd": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-07-04 15:26:24.457745",
|
"modified": "2023-11-07 10:11:49.126789",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Quiz",
|
"name": "LMS Quiz",
|
||||||
@@ -123,6 +151,18 @@
|
|||||||
"role": "Moderator",
|
"role": "Moderator",
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Course Creator",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"show_title_field_in_link": 1,
|
"show_title_field_in_link": 1,
|
||||||
|
|||||||
@@ -5,18 +5,32 @@ import json
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import cstr
|
from frappe.utils import cstr, comma_and
|
||||||
from lms.lms.utils import generate_slug, has_course_moderator_role, can_create_courses
|
from lms.lms.doctype.lms_question.lms_question import validate_correct_answers
|
||||||
|
from lms.lms.utils import (
|
||||||
|
generate_slug,
|
||||||
|
has_course_moderator_role,
|
||||||
|
has_course_instructor_role,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class LMSQuiz(Document):
|
class LMSQuiz(Document):
|
||||||
|
def validate(self):
|
||||||
|
self.validate_duplicate_questions()
|
||||||
|
self.total_marks = set_total_marks(self.name, self.questions)
|
||||||
|
|
||||||
|
def validate_duplicate_questions(self):
|
||||||
|
questions = [row.question for row in self.questions]
|
||||||
|
rows = [i + 1 for i, x in enumerate(questions) if questions.count(x) > 1]
|
||||||
|
if len(rows):
|
||||||
|
frappe.throw(
|
||||||
|
_("Rows {0} have the duplicate questions.").format(frappe.bold(comma_and(rows)))
|
||||||
|
)
|
||||||
|
|
||||||
def autoname(self):
|
def autoname(self):
|
||||||
if not self.name:
|
if not self.name:
|
||||||
self.name = generate_slug(self.title, "LMS Quiz")
|
self.name = generate_slug(self.title, "LMS Quiz")
|
||||||
|
|
||||||
def validate(self):
|
|
||||||
validate_correct_answers(self.questions)
|
|
||||||
|
|
||||||
def get_last_submission_details(self):
|
def get_last_submission_details(self):
|
||||||
"""Returns the latest submission for this user."""
|
"""Returns the latest submission for this user."""
|
||||||
user = frappe.session.user
|
user = frappe.session.user
|
||||||
@@ -35,76 +49,11 @@ class LMSQuiz(Document):
|
|||||||
return result[0]
|
return result[0]
|
||||||
|
|
||||||
|
|
||||||
def get_correct_options(question):
|
def set_total_marks(quiz, questions):
|
||||||
correct_option_fields = [
|
marks = 0
|
||||||
"is_correct_1",
|
|
||||||
"is_correct_2",
|
|
||||||
"is_correct_3",
|
|
||||||
"is_correct_4",
|
|
||||||
]
|
|
||||||
return list(filter(lambda x: question.get(x) == 1, correct_option_fields))
|
|
||||||
|
|
||||||
|
|
||||||
def validate_correct_answers(questions):
|
|
||||||
for question in questions:
|
for question in questions:
|
||||||
if question.type == "Choices":
|
marks += question.get("marks")
|
||||||
validate_duplicate_options(question)
|
return marks
|
||||||
validate_correct_options(question)
|
|
||||||
else:
|
|
||||||
validate_possible_answer(question)
|
|
||||||
|
|
||||||
|
|
||||||
def validate_duplicate_options(question):
|
|
||||||
options = []
|
|
||||||
|
|
||||||
for num in range(1, 5):
|
|
||||||
if question.get(f"option_{num}"):
|
|
||||||
options.append(question.get(f"option_{num}"))
|
|
||||||
|
|
||||||
if len(set(options)) != len(options):
|
|
||||||
frappe.throw(
|
|
||||||
_("Duplicate options found for this question: {0}").format(
|
|
||||||
frappe.bold(question.question)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def validate_correct_options(question):
|
|
||||||
correct_options = get_correct_options(question)
|
|
||||||
|
|
||||||
if len(correct_options) > 1:
|
|
||||||
question.multiple = 1
|
|
||||||
|
|
||||||
if not len(correct_options):
|
|
||||||
frappe.throw(
|
|
||||||
_("At least one option must be correct for this question: {0}").format(
|
|
||||||
frappe.bold(question.question)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def validate_possible_answer(question):
|
|
||||||
possible_answers_fields = [
|
|
||||||
"possibility_1",
|
|
||||||
"possibility_2",
|
|
||||||
"possibility_3",
|
|
||||||
"possibility_4",
|
|
||||||
]
|
|
||||||
possible_answers = list(filter(lambda x: question.get(x), possible_answers_fields))
|
|
||||||
|
|
||||||
if not len(possible_answers):
|
|
||||||
frappe.throw(
|
|
||||||
_("Add at least one possible answer for this question: {0}").format(
|
|
||||||
frappe.bold(question.question)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def update_lesson_info(doc, method):
|
|
||||||
if doc.quiz_id:
|
|
||||||
frappe.db.set_value(
|
|
||||||
"LMS Quiz", doc.quiz_id, {"lesson": doc.name, "course": doc.course}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@@ -114,45 +63,72 @@ def quiz_summary(quiz, results):
|
|||||||
|
|
||||||
for result in results:
|
for result in results:
|
||||||
correct = result["is_correct"][0]
|
correct = result["is_correct"][0]
|
||||||
result["question"] = frappe.db.get_value(
|
|
||||||
"LMS Quiz Question",
|
|
||||||
{"parent": quiz, "idx": result["question_index"] + 1},
|
|
||||||
["question"],
|
|
||||||
)
|
|
||||||
|
|
||||||
for point in result["is_correct"]:
|
for point in result["is_correct"]:
|
||||||
correct = correct and point
|
correct = correct and point
|
||||||
|
|
||||||
result["is_correct"] = correct
|
result["is_correct"] = correct
|
||||||
score += correct
|
|
||||||
|
question_details = frappe.db.get_value(
|
||||||
|
"LMS Quiz Question",
|
||||||
|
{"parent": quiz, "idx": result["question_index"] + 1},
|
||||||
|
["question", "marks"],
|
||||||
|
as_dict=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
result["question_name"] = question_details.question
|
||||||
|
result["question"] = frappe.db.get_value(
|
||||||
|
"LMS Question", question_details.question, "question"
|
||||||
|
)
|
||||||
|
marks = question_details.marks if correct else 0
|
||||||
|
|
||||||
|
result["marks"] = marks
|
||||||
|
score += marks
|
||||||
|
|
||||||
del result["question_index"]
|
del result["question_index"]
|
||||||
|
|
||||||
|
quiz_details = frappe.db.get_value(
|
||||||
|
"LMS Quiz", quiz, ["total_marks", "passing_percentage"], as_dict=1
|
||||||
|
)
|
||||||
|
score_out_of = quiz_details.total_marks
|
||||||
|
percentage = (score / score_out_of) * 100
|
||||||
|
|
||||||
submission = frappe.get_doc(
|
submission = frappe.get_doc(
|
||||||
{
|
{
|
||||||
"doctype": "LMS Quiz Submission",
|
"doctype": "LMS Quiz Submission",
|
||||||
"quiz": quiz,
|
"quiz": quiz,
|
||||||
"result": results,
|
"result": results,
|
||||||
"score": score,
|
"score": score,
|
||||||
|
"score_out_of": score_out_of,
|
||||||
"member": frappe.session.user,
|
"member": frappe.session.user,
|
||||||
|
"percentage": percentage,
|
||||||
|
"passing_percentage": quiz_details.passing_percentage,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
submission.save(ignore_permissions=True)
|
submission.save(ignore_permissions=True)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"score": score,
|
"score": score,
|
||||||
|
"score_out_of": score_out_of,
|
||||||
"submission": submission.name,
|
"submission": submission.name,
|
||||||
|
"pass": percentage == quiz_details.passing_percentage,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def save_quiz(
|
def save_quiz(
|
||||||
quiz_title, max_attempts=1, quiz=None, show_answers=1, show_submission_history=0
|
quiz_title,
|
||||||
|
passing_percentage,
|
||||||
|
questions,
|
||||||
|
max_attempts=0,
|
||||||
|
quiz=None,
|
||||||
|
show_answers=1,
|
||||||
|
show_submission_history=0,
|
||||||
):
|
):
|
||||||
if not can_create_courses():
|
if not has_course_moderator_role() or not has_course_instructor_role():
|
||||||
return
|
return
|
||||||
|
|
||||||
values = {
|
values = {
|
||||||
"title": quiz_title,
|
"title": quiz_title,
|
||||||
|
"passing_percentage": passing_percentage,
|
||||||
"max_attempts": max_attempts,
|
"max_attempts": max_attempts,
|
||||||
"show_answers": show_answers,
|
"show_answers": show_answers,
|
||||||
"show_submission_history": show_submission_history,
|
"show_submission_history": show_submission_history,
|
||||||
@@ -160,38 +136,74 @@ def save_quiz(
|
|||||||
|
|
||||||
if quiz:
|
if quiz:
|
||||||
frappe.db.set_value("LMS Quiz", quiz, values)
|
frappe.db.set_value("LMS Quiz", quiz, values)
|
||||||
|
update_questions(quiz, questions)
|
||||||
return quiz
|
return quiz
|
||||||
else:
|
else:
|
||||||
doc = frappe.new_doc("LMS Quiz")
|
doc = frappe.new_doc("LMS Quiz")
|
||||||
doc.update(values)
|
doc.update(values)
|
||||||
doc.save(ignore_permissions=True)
|
doc.save()
|
||||||
|
update_questions(doc.name, questions)
|
||||||
return doc.name
|
return doc.name
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
def update_questions(quiz, questions):
|
||||||
def save_question(quiz, values, index):
|
questions = json.loads(questions)
|
||||||
values = frappe._dict(json.loads(values))
|
|
||||||
validate_correct_answers([values])
|
|
||||||
|
|
||||||
if values.get("name"):
|
delete_questions(quiz, questions)
|
||||||
doc = frappe.get_doc("LMS Quiz Question", values.get("name"))
|
add_questions(quiz, questions)
|
||||||
else:
|
frappe.db.set_value("LMS Quiz", quiz, "total_marks", set_total_marks(quiz, questions))
|
||||||
doc = frappe.new_doc("LMS Quiz Question")
|
|
||||||
|
|
||||||
doc.update(
|
|
||||||
|
def delete_questions(quiz, questions):
|
||||||
|
existing_questions = frappe.get_all(
|
||||||
|
"LMS Quiz Question",
|
||||||
{
|
{
|
||||||
"question": values["question"],
|
"parent": quiz,
|
||||||
"type": values["type"],
|
},
|
||||||
}
|
pluck="name",
|
||||||
)
|
)
|
||||||
|
|
||||||
if not values.get("name"):
|
current_questions = [question.get("question_name") for question in questions]
|
||||||
|
|
||||||
|
for question in existing_questions:
|
||||||
|
if question not in current_questions:
|
||||||
|
frappe.db.delete("LMS Quiz Question", question)
|
||||||
|
|
||||||
|
|
||||||
|
def add_questions(quiz, questions):
|
||||||
|
for index, question in enumerate(questions):
|
||||||
|
question = frappe._dict(question)
|
||||||
|
if question.question_name:
|
||||||
|
doc = frappe.get_doc("LMS Quiz Question", question.question_name)
|
||||||
|
else:
|
||||||
|
doc = frappe.new_doc("LMS Quiz Question")
|
||||||
doc.update(
|
doc.update(
|
||||||
{
|
{
|
||||||
"parent": quiz,
|
"parent": quiz,
|
||||||
"parenttype": "LMS Quiz",
|
"parenttype": "LMS Quiz",
|
||||||
"parentfield": "questions",
|
"parentfield": "questions",
|
||||||
"idx": index,
|
"idx": index + 1,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
doc.update({"question": question.question, "marks": question.marks})
|
||||||
|
|
||||||
|
doc.save()
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def save_question(quiz, values, index):
|
||||||
|
values = frappe._dict(json.loads(values))
|
||||||
|
|
||||||
|
if values.get("name"):
|
||||||
|
doc = frappe.get_doc("LMS Question", values.get("name"))
|
||||||
|
else:
|
||||||
|
doc = frappe.new_doc("LMS Question")
|
||||||
|
|
||||||
|
doc.update(
|
||||||
|
{
|
||||||
|
"question": values.question,
|
||||||
|
"type": values["type"],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -218,9 +230,8 @@ def save_question(quiz, values, index):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
doc.save(ignore_permissions=True)
|
doc.save()
|
||||||
|
return doc.name
|
||||||
return quiz
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@@ -253,9 +264,7 @@ def check_choice_answers(question, answers):
|
|||||||
fields.append(f"option_{cstr(num)}")
|
fields.append(f"option_{cstr(num)}")
|
||||||
fields.append(f"is_correct_{cstr(num)}")
|
fields.append(f"is_correct_{cstr(num)}")
|
||||||
|
|
||||||
question_details = frappe.db.get_value(
|
question_details = frappe.db.get_value("LMS Question", question, fields, as_dict=1)
|
||||||
"LMS Quiz Question", question, fields, as_dict=1
|
|
||||||
)
|
|
||||||
|
|
||||||
for num in range(1, 5):
|
for num in range(1, 5):
|
||||||
if question_details[f"option_{num}"] in answers:
|
if question_details[f"option_{num}"] in answers:
|
||||||
@@ -271,9 +280,7 @@ def check_input_answers(question, answer):
|
|||||||
for num in range(1, 5):
|
for num in range(1, 5):
|
||||||
fields.append(f"possibility_{cstr(num)}")
|
fields.append(f"possibility_{cstr(num)}")
|
||||||
|
|
||||||
question_details = frappe.db.get_value(
|
question_details = frappe.db.get_value("LMS Question", question, fields, as_dict=1)
|
||||||
"LMS Quiz Question", question, fields, as_dict=1
|
|
||||||
)
|
|
||||||
for num in range(1, 5):
|
for num in range(1, 5):
|
||||||
current_possibility = question_details[f"possibility_{num}"]
|
current_possibility = question_details[f"possibility_{num}"]
|
||||||
if current_possibility and current_possibility.lower() == answer.lower():
|
if current_possibility and current_possibility.lower() == answer.lower():
|
||||||
|
|||||||
@@ -10,51 +10,36 @@ import frappe
|
|||||||
class TestLMSQuiz(unittest.TestCase):
|
class TestLMSQuiz(unittest.TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls) -> None:
|
def setUpClass(cls) -> None:
|
||||||
frappe.get_doc({"doctype": "LMS Quiz", "title": "Test Quiz"}).save(
|
frappe.get_doc(
|
||||||
ignore_permissions=True
|
{"doctype": "LMS Quiz", "title": "Test Quiz", "passing_percentage": 90}
|
||||||
)
|
).save(ignore_permissions=True)
|
||||||
|
|
||||||
def test_with_multiple_options(self):
|
def test_with_multiple_options(self):
|
||||||
quiz = frappe.get_doc("LMS Quiz", "test-quiz")
|
question = frappe.new_doc("LMS Question")
|
||||||
quiz.append(
|
question.question = "Question Multiple"
|
||||||
"questions",
|
question.type = "Choices"
|
||||||
{
|
question.option_1 = "Option 1"
|
||||||
"question": "Question Multiple",
|
question.is_correct_1 = 1
|
||||||
"type": "Choices",
|
question.option_2 = "Option 2"
|
||||||
"option_1": "Option 1",
|
question.is_correct_2 = 1
|
||||||
"is_correct_1": 1,
|
question.save()
|
||||||
"option_2": "Option 2",
|
self.assertTrue(question.multiple)
|
||||||
"is_correct_2": 1,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
quiz.save()
|
|
||||||
self.assertTrue(quiz.questions[0].multiple)
|
|
||||||
|
|
||||||
def test_with_no_correct_option(self):
|
def test_with_no_correct_option(self):
|
||||||
quiz = frappe.get_doc("LMS Quiz", "test-quiz")
|
question = frappe.new_doc("LMS Question")
|
||||||
quiz.append(
|
question.question = "Question Multiple"
|
||||||
"questions",
|
question.type = "Choices"
|
||||||
{
|
question.option_1 = "Option 1"
|
||||||
"question": "Question no correct option",
|
question.option_2 = "Option 2"
|
||||||
"type": "Choices",
|
self.assertRaises(frappe.ValidationError, question.save)
|
||||||
"option_1": "Option 1",
|
|
||||||
"option_2": "Option 2",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
self.assertRaises(frappe.ValidationError, quiz.save)
|
|
||||||
|
|
||||||
def test_with_no_possible_answers(self):
|
def test_with_no_possible_answers(self):
|
||||||
quiz = frappe.get_doc("LMS Quiz", "test-quiz")
|
question = frappe.new_doc("LMS Question")
|
||||||
quiz.append(
|
question.question = "Question Multiple"
|
||||||
"questions",
|
question.type = "User Input"
|
||||||
{
|
self.assertRaises(frappe.ValidationError, question.save)
|
||||||
"question": "Question Possible Answers",
|
|
||||||
"type": "User Input",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
self.assertRaises(frappe.ValidationError, quiz.save)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def tearDownClass(cls) -> None:
|
def tearDownClass(cls) -> None:
|
||||||
frappe.db.delete("LMS Quiz", "test-quiz")
|
frappe.db.delete("LMS Quiz", "test-quiz")
|
||||||
frappe.db.delete("LMS Quiz Question", {"parent": "test-quiz"})
|
frappe.db.delete("LMS Question")
|
||||||
|
|||||||
@@ -6,208 +6,31 @@
|
|||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"question",
|
"question",
|
||||||
"type",
|
"marks"
|
||||||
"options_section",
|
|
||||||
"option_1",
|
|
||||||
"is_correct_1",
|
|
||||||
"column_break_5",
|
|
||||||
"explanation_1",
|
|
||||||
"section_break_5",
|
|
||||||
"option_2",
|
|
||||||
"is_correct_2",
|
|
||||||
"column_break_10",
|
|
||||||
"explanation_2",
|
|
||||||
"column_break_4",
|
|
||||||
"option_3",
|
|
||||||
"is_correct_3",
|
|
||||||
"column_break_15",
|
|
||||||
"explanation_3",
|
|
||||||
"section_break_11",
|
|
||||||
"option_4",
|
|
||||||
"is_correct_4",
|
|
||||||
"column_break_20",
|
|
||||||
"explanation_4",
|
|
||||||
"section_break_mnhr",
|
|
||||||
"possibility_1",
|
|
||||||
"possibility_3",
|
|
||||||
"column_break_vnaj",
|
|
||||||
"possibility_2",
|
|
||||||
"possibility_4",
|
|
||||||
"section_break_c1lf",
|
|
||||||
"multiple"
|
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldname": "question",
|
"fieldname": "question",
|
||||||
"fieldtype": "Text Editor",
|
"fieldtype": "Link",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Question",
|
"label": "Question",
|
||||||
|
"options": "LMS Question",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "option_1",
|
"default": "1",
|
||||||
"fieldtype": "Small Text",
|
"fieldname": "marks",
|
||||||
"label": "Option 1",
|
"fieldtype": "Int",
|
||||||
"mandatory_depends_on": "eval: doc.type == 'Choices'"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "option_2",
|
|
||||||
"fieldtype": "Small Text",
|
|
||||||
"label": "Option 2",
|
|
||||||
"mandatory_depends_on": "eval: doc.type == 'Choices'"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "option_3",
|
|
||||||
"fieldtype": "Small Text",
|
|
||||||
"label": "Option 3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "option_4",
|
|
||||||
"fieldtype": "Small Text",
|
|
||||||
"label": "Option 4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"depends_on": "option_1",
|
|
||||||
"fieldname": "is_correct_1",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Is Correct"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"depends_on": "option_2",
|
|
||||||
"fieldname": "is_correct_2",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Is Correct"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"depends_on": "option_3",
|
|
||||||
"fieldname": "is_correct_3",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Is Correct"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"depends_on": "option_4",
|
|
||||||
"fieldname": "is_correct_4",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Is Correct"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "multiple",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"hidden": 1,
|
|
||||||
"label": "Multiple Correct Answers",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "eval: doc.type == 'Choices'",
|
|
||||||
"fieldname": "options_section",
|
|
||||||
"fieldtype": "Section Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "eval: doc.type == 'Choices'",
|
|
||||||
"fieldname": "column_break_4",
|
|
||||||
"fieldtype": "Section Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "eval: doc.type == 'Choices'",
|
|
||||||
"fieldname": "section_break_5",
|
|
||||||
"fieldtype": "Section Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "eval: doc.type == 'Choices'",
|
|
||||||
"fieldname": "section_break_11",
|
|
||||||
"fieldtype": "Section Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "option_1",
|
|
||||||
"fieldname": "explanation_1",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"label": "Explanation"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "option_2",
|
|
||||||
"fieldname": "explanation_2",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"label": "Explanation"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "option_3",
|
|
||||||
"fieldname": "explanation_3",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"label": "Explanation"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "option_4",
|
|
||||||
"fieldname": "explanation_4",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"label": "Explanation"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "column_break_5",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "column_break_10",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "column_break_15",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "column_break_20",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "type",
|
|
||||||
"fieldtype": "Select",
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Type",
|
"label": "Marks",
|
||||||
"options": "Choices\nUser Input"
|
"non_negative": 1,
|
||||||
},
|
"reqd": 1
|
||||||
{
|
|
||||||
"depends_on": "eval: doc.type == 'User Input'",
|
|
||||||
"fieldname": "section_break_mnhr",
|
|
||||||
"fieldtype": "Section Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "possibility_1",
|
|
||||||
"fieldtype": "Small Text",
|
|
||||||
"label": "Possible Answer 1",
|
|
||||||
"mandatory_depends_on": "eval: doc.type == 'User Input'"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "possibility_2",
|
|
||||||
"fieldtype": "Small Text",
|
|
||||||
"label": "Possible Answer 2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "possibility_3",
|
|
||||||
"fieldtype": "Small Text",
|
|
||||||
"label": "Possible Answer 3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "possibility_4",
|
|
||||||
"fieldtype": "Small Text",
|
|
||||||
"label": "Possible Answer 4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "section_break_c1lf",
|
|
||||||
"fieldtype": "Section Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "column_break_vnaj",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-07-04 16:43:49.837134",
|
"modified": "2023-10-16 19:51:03.893144",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Quiz Question",
|
"name": "LMS Quiz Question",
|
||||||
|
|||||||
@@ -6,7 +6,11 @@
|
|||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"question",
|
"question",
|
||||||
|
"section_break_fztv",
|
||||||
|
"question_name",
|
||||||
"answer",
|
"answer",
|
||||||
|
"column_break_flus",
|
||||||
|
"marks",
|
||||||
"is_correct"
|
"is_correct"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
@@ -31,12 +35,33 @@
|
|||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Is Correct",
|
"label": "Is Correct",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_fztv",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "question_name",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Question Name",
|
||||||
|
"options": "LMS Question"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_flus",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "marks",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Marks",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-11-24 11:15:45.931119",
|
"modified": "2023-10-17 11:55:25.641214",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Quiz Result",
|
"name": "LMS Quiz Result",
|
||||||
|
|||||||
@@ -6,11 +6,16 @@
|
|||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"quiz",
|
"quiz",
|
||||||
"score",
|
|
||||||
"course",
|
"course",
|
||||||
"column_break_3",
|
"column_break_3",
|
||||||
"member",
|
"member",
|
||||||
"member_name",
|
"member_name",
|
||||||
|
"section_break_dkpn",
|
||||||
|
"score",
|
||||||
|
"score_out_of",
|
||||||
|
"column_break_gkip",
|
||||||
|
"percentage",
|
||||||
|
"passing_percentage",
|
||||||
"section_break_6",
|
"section_break_6",
|
||||||
"result"
|
"result"
|
||||||
],
|
],
|
||||||
@@ -31,9 +36,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "score",
|
"fieldname": "score",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Int",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Score"
|
"label": "Score",
|
||||||
|
"read_only": 1,
|
||||||
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "member",
|
"fieldname": "member",
|
||||||
@@ -65,12 +72,45 @@
|
|||||||
"label": "Course",
|
"label": "Course",
|
||||||
"options": "LMS Course",
|
"options": "LMS Course",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "quiz.total_marks",
|
||||||
|
"fieldname": "score_out_of",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Score Out Of",
|
||||||
|
"non_negative": 1,
|
||||||
|
"read_only": 1,
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_dkpn",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_gkip",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "percentage",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Percentage",
|
||||||
|
"non_negative": 1,
|
||||||
|
"read_only": 1,
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "passing_percentage",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Passing Percentage",
|
||||||
|
"non_negative": 1,
|
||||||
|
"read_only": 1,
|
||||||
|
"reqd": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"in_create": 1,
|
"in_create": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-11-15 15:27:07.770945",
|
"modified": "2023-10-17 13:07:27.979975",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Quiz Submission",
|
"name": "LMS Quiz Submission",
|
||||||
|
|||||||
@@ -6,4 +6,10 @@ from frappe.model.document import Document
|
|||||||
|
|
||||||
|
|
||||||
class LMSQuizSubmission(Document):
|
class LMSQuizSubmission(Document):
|
||||||
pass
|
def before_insert(self):
|
||||||
|
if not self.percentage:
|
||||||
|
self.set_percentage()
|
||||||
|
|
||||||
|
def set_percentage(self):
|
||||||
|
if self.score and self.score_out_of:
|
||||||
|
self.percentage = (self.score / self.score_out_of) * 100
|
||||||
|
|||||||
@@ -16,14 +16,15 @@
|
|||||||
"portal_course_creation",
|
"portal_course_creation",
|
||||||
"section_break_szgq",
|
"section_break_szgq",
|
||||||
"send_calendar_invite_for_evaluations",
|
"send_calendar_invite_for_evaluations",
|
||||||
"column_break_2",
|
|
||||||
"allow_student_progress",
|
"allow_student_progress",
|
||||||
"payment_section",
|
"column_break_2",
|
||||||
"razorpay_key",
|
"show_dashboard",
|
||||||
"default_currency",
|
"show_courses",
|
||||||
"column_break_cfcv",
|
"show_students",
|
||||||
"razorpay_secret",
|
"show_assessments",
|
||||||
"apply_gst",
|
"show_live_class",
|
||||||
|
"show_discussions",
|
||||||
|
"show_emails",
|
||||||
"signup_settings_tab",
|
"signup_settings_tab",
|
||||||
"signup_settings_section",
|
"signup_settings_section",
|
||||||
"terms_of_use",
|
"terms_of_use",
|
||||||
@@ -38,7 +39,22 @@
|
|||||||
"mentor_request_tab",
|
"mentor_request_tab",
|
||||||
"mentor_request_section",
|
"mentor_request_section",
|
||||||
"mentor_request_creation",
|
"mentor_request_creation",
|
||||||
"mentor_request_status_update"
|
"mentor_request_status_update",
|
||||||
|
"payment_settings_tab",
|
||||||
|
"payment_section",
|
||||||
|
"razorpay_key",
|
||||||
|
"razorpay_secret",
|
||||||
|
"apply_gst",
|
||||||
|
"column_break_cfcv",
|
||||||
|
"default_currency",
|
||||||
|
"show_usd_equivalent",
|
||||||
|
"apply_rounding",
|
||||||
|
"exception_country",
|
||||||
|
"email_templates_tab",
|
||||||
|
"certification_template",
|
||||||
|
"batch_confirmation_template",
|
||||||
|
"column_break_uwsp",
|
||||||
|
"assignment_submission_template"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -67,7 +83,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_2",
|
"fieldname": "column_break_2",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break",
|
||||||
|
"label": "Show Tab in Batch"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "search_placeholder",
|
"fieldname": "search_placeholder",
|
||||||
@@ -173,7 +190,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "section_break_szgq",
|
"fieldname": "section_break_szgq",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Class Settings"
|
"label": "Batch Settings"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "signup_settings_tab",
|
"fieldname": "signup_settings_tab",
|
||||||
@@ -183,6 +200,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "mentor_request_tab",
|
"fieldname": "mentor_request_tab",
|
||||||
"fieldtype": "Tab Break",
|
"fieldtype": "Tab Break",
|
||||||
|
"hidden": 1,
|
||||||
"label": "Mentor Request"
|
"label": "Mentor Request"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -194,8 +212,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "payment_section",
|
"fieldname": "payment_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break"
|
||||||
"label": "Payment"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "default_currency",
|
"fieldname": "default_currency",
|
||||||
@@ -231,12 +248,105 @@
|
|||||||
"fieldname": "apply_gst",
|
"fieldname": "apply_gst",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Apply GST for India"
|
"label": "Apply GST for India"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "show_usd_equivalent",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Show USD Equivalent"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "show_usd_equivalent",
|
||||||
|
"fieldname": "exception_country",
|
||||||
|
"fieldtype": "Table MultiSelect",
|
||||||
|
"label": "Maintain Original Currency",
|
||||||
|
"options": "Payment Country"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "apply_rounding",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Apply Rounding on Equivalent"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "batch_confirmation_template",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Batch Confirmation Template",
|
||||||
|
"options": "Email Template"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"fieldname": "show_courses",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Courses"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"fieldname": "show_students",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Students"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"fieldname": "show_assessments",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Assessments"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"fieldname": "show_live_class",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Live Class"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"fieldname": "show_discussions",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Discussions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"fieldname": "show_emails",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Emails"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "payment_settings_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Payment Settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"fieldname": "show_dashboard",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Dashboard"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "certification_template",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Certificate Email Template",
|
||||||
|
"options": "Email Template"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "email_templates_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Email Templates"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "assignment_submission_template",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Assignment Submission Template",
|
||||||
|
"options": "Email Template"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_uwsp",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-08-29 09:54:48.030823",
|
"modified": "2023-11-07 11:23:14.257687",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Settings",
|
"name": "LMS Settings",
|
||||||
|
|||||||
0
lms/lms/doctype/lms_source/__init__.py
Normal file
0
lms/lms/doctype/lms_source/__init__.py
Normal file
8
lms/lms/doctype/lms_source/lms_source.js
Normal file
8
lms/lms/doctype/lms_source/lms_source.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2023, Frappe and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("LMS Source", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
69
lms/lms/doctype/lms_source/lms_source.json
Normal file
69
lms/lms/doctype/lms_source/lms_source.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"autoname": "field:source",
|
||||||
|
"creation": "2023-10-26 16:28:53.932278",
|
||||||
|
"default_view": "List",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"source"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "source",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Source",
|
||||||
|
"unique": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2023-10-26 17:25:09.144367",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "LMS",
|
||||||
|
"name": "LMS Source",
|
||||||
|
"naming_rule": "By fieldname",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Moderator",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "LMS Student",
|
||||||
|
"select": 1,
|
||||||
|
"share": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
|
"title_field": "source"
|
||||||
|
}
|
||||||
9
lms/lms/doctype/lms_source/lms_source.py
Normal file
9
lms/lms/doctype/lms_source/lms_source.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2023, Frappe and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class LMSSource(Document):
|
||||||
|
pass
|
||||||
9
lms/lms/doctype/lms_source/test_lms_source.py
Normal file
9
lms/lms/doctype/lms_source/test_lms_source.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2023, Frappe and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestLMSSource(FrappeTestCase):
|
||||||
|
pass
|
||||||
0
lms/lms/doctype/lms_timetable_legend/__init__.py
Normal file
0
lms/lms/doctype/lms_timetable_legend/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2023, Frappe and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("LMS Timetable Legend", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"autoname": "hash",
|
||||||
|
"creation": "2023-10-11 16:36:45.079267",
|
||||||
|
"default_view": "List",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"reference_doctype",
|
||||||
|
"label",
|
||||||
|
"color"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "reference_doctype",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Reference DocType",
|
||||||
|
"options": "DocType",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "color",
|
||||||
|
"fieldtype": "Color",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Color",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "label",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Label",
|
||||||
|
"reqd": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2023-10-11 17:15:37.039139",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "LMS",
|
||||||
|
"name": "LMS Timetable Legend",
|
||||||
|
"naming_rule": "Random",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2023, Frappe and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class LMSTimetableLegend(Document):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2023, Frappe and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestLMSTimetableLegend(FrappeTestCase):
|
||||||
|
pass
|
||||||
0
lms/lms/doctype/lms_timetable_template/__init__.py
Normal file
0
lms/lms/doctype/lms_timetable_template/__init__.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// Copyright (c) 2023, Frappe and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on("LMS Timetable Template", {
|
||||||
|
refresh(frm) {
|
||||||
|
frm.set_query("reference_doctype", "timetable", function () {
|
||||||
|
let doctypes = ["Course Lesson", "LMS Quiz", "LMS Assignment"];
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
name: ["in", doctypes],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.set_query("reference_doctype", "timetable_legends", function () {
|
||||||
|
let doctypes = [
|
||||||
|
"Course Lesson",
|
||||||
|
"LMS Quiz",
|
||||||
|
"LMS Assignment",
|
||||||
|
"LMS Live Class",
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
name: ["in", doctypes],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"autoname": "hash",
|
||||||
|
"creation": "2023-09-18 14:16:16.964077",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"title",
|
||||||
|
"timetable",
|
||||||
|
"timetable_legends"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "title",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Title"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "timetable",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Timetable",
|
||||||
|
"options": "LMS Batch Timetable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "timetable_legends",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Timetable Legends",
|
||||||
|
"options": "LMS Timetable Legend"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2023-10-11 17:09:05.096243",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "LMS",
|
||||||
|
"name": "LMS Timetable Template",
|
||||||
|
"naming_rule": "Random",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Moderator",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"show_title_field_in_link": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
|
"title_field": "title"
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2023, Frappe and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class LMSTimetableTemplate(Document):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2023, Frappe and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestLMSTimetableTemplate(FrappeTestCase):
|
||||||
|
pass
|
||||||
0
lms/lms/doctype/payment_country/__init__.py
Normal file
0
lms/lms/doctype/payment_country/__init__.py
Normal file
8
lms/lms/doctype/payment_country/payment_country.js
Normal file
8
lms/lms/doctype/payment_country/payment_country.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2023, Frappe and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("Payment Country", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
33
lms/lms/doctype/payment_country/payment_country.json
Normal file
33
lms/lms/doctype/payment_country/payment_country.json
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"creation": "2023-09-11 11:53:16.253740",
|
||||||
|
"default_view": "List",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"country"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "country",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Country",
|
||||||
|
"options": "Country"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2023-09-11 12:04:56.048632",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "LMS",
|
||||||
|
"name": "Payment Country",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
||||||
9
lms/lms/doctype/payment_country/payment_country.py
Normal file
9
lms/lms/doctype/payment_country/payment_country.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2023, Frappe and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentCountry(Document):
|
||||||
|
pass
|
||||||
9
lms/lms/doctype/payment_country/test_payment_country.py
Normal file
9
lms/lms/doctype/payment_country/test_payment_country.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2023, Frappe and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestPaymentCountry(FrappeTestCase):
|
||||||
|
pass
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-12-21 09:35:44.265910",
|
"modified": "2023-09-29 17:05:50.502696",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "User Skill",
|
"name": "User Skill",
|
||||||
@@ -44,11 +44,12 @@
|
|||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "All",
|
"role": "LMS Student",
|
||||||
"select": 1,
|
"select": 1,
|
||||||
"share": 1
|
"share": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC"
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
}
|
}
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
{
|
|
||||||
"attach_print": 0,
|
|
||||||
"channel": "Email",
|
|
||||||
"creation": "2023-03-27 16:34:03.505647",
|
|
||||||
"days_in_advance": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Notification",
|
|
||||||
"document_type": "LMS Assignment Submission",
|
|
||||||
"enabled": 1,
|
|
||||||
"event": "New",
|
|
||||||
"idx": 0,
|
|
||||||
"is_standard": 1,
|
|
||||||
"message": "<h3> {{ _(\"Assignment Submission\") }}\n\n{% set title = frappe.db.get_value(\"Course Lesson\", doc.lesson, \"title\") %}\n\n<p> {{ _(\"{0} has submitted their assignment for the lesson {1}\").format(doc.member_name, title) }} </p>\n\n <p> {{ _(\" Please evaluate and grade the assignment. \") }} </p>",
|
|
||||||
"modified": "2023-03-27 16:46:44.564007",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "LMS",
|
|
||||||
"name": "Assignment Submission Notification",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"recipients": [
|
|
||||||
{
|
|
||||||
"receiver_by_document_field": "evaluator"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"send_system_notification": 0,
|
|
||||||
"send_to_all_assignees": 0,
|
|
||||||
"subject": "Assignment Submission"
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
<div style="background-color: #f4f5f6; padding: 1rem;">
|
|
||||||
<div style="background-color: #ffffff; width: 75%; margin: 0 auto; padding: 1rem;">
|
|
||||||
<h3> {{ _("Assignment Submission") }} </h3>
|
|
||||||
{% set title = frappe.db.get_value("Course Lesson", doc.lesson, "title") %}
|
|
||||||
<br>
|
|
||||||
<p> {{ _("{0} has submitted their assignment for the lesson {1}").format(frappe.bold(doc.member_name), frappe.bold(title)) }}
|
|
||||||
</p>
|
|
||||||
<p> {{ _(" Please evaluate and grade the assignment.") }} </p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import frappe
|
|
||||||
|
|
||||||
|
|
||||||
def get_context(context):
|
|
||||||
# do your magic here
|
|
||||||
pass
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"absolute_value": 0,
|
"absolute_value": 0,
|
||||||
"align_labels_right": 0,
|
"align_labels_right": 0,
|
||||||
"creation": "2023-08-09 17:02:21.430320",
|
"creation": "2023-08-09 17:02:21.430320",
|
||||||
"css": ".outer-border {\n font-family: \"Inter\" sans-serif;\n font-size: 16px;\n border-radius: 0.5rem;\n border: 1px solid #E2E6E9;\n padding: 1rem;\n}\n\n.inner-border {\n border: 10px solid #0089FF;\n border-radius: 8px;\n text-align: center;\n padding: 6rem 4rem;\n background-color: #FFFFFF;\n}\n\n.certificate-logo {\n height: 1.5rem;\n margin-bottom: 4rem;\n}\n\n.certificate-name {\n font-size: 2rem;\n font-weight: 500;\n color: #192734;\n margin-bottom: 0.5rem;\n}\n\n.certificate-footer {\n margin: 4rem auto 0;\n width: 70%;\n text-align: center;\n}\n\n.certificate-footer-item {\n color: #192734;\n}\n\n.cursive-font {\n font-family: cursive;\n font-weight: 600;\n}\n\n.certificate-divider {\n margin: 0.5rem 0;\n}\n\n.certificate-expiry {\n margin-left: 2rem;\n}",
|
"css": ".outer-border {\n font-family: \"Inter\" sans-serif;\n font-size: 16px;\n border-radius: 0.5rem;\n border: 1px solid #E2E6E9;\n padding: 1rem;\n}\n\n.inner-border {\n border: 8px solid #0089FF;\n border-radius: 8px;\n text-align: center;\n padding: 6rem 4rem;\n background-color: #FFFFFF;\n}\n\n.certificate-logo {\n height: 1.5rem;\n margin-bottom: 4rem;\n}\n\n.certificate-name {\n font-size: 2rem;\n font-weight: 500;\n color: #192734;\n margin-bottom: 0.5rem;\n}\n\n.certificate-footer {\n margin: 4rem auto 0;\n width: 70%;\n text-align: center;\n}\n\n.certificate-footer-item {\n color: #192734;\n}\n\n.cursive-font {\n font-family: cursive;\n font-weight: 600;\n}\n\n.certificate-divider {\n margin: 0.5rem 0;\n}\n\n.certificate-expiry {\n margin-left: 2rem;\n}",
|
||||||
"custom_format": 1,
|
"custom_format": 1,
|
||||||
"disabled": 0,
|
"disabled": 0,
|
||||||
"doc_type": "LMS Certificate",
|
"doc_type": "LMS Certificate",
|
||||||
@@ -10,19 +10,20 @@
|
|||||||
"doctype": "Print Format",
|
"doctype": "Print Format",
|
||||||
"font_size": 14,
|
"font_size": 14,
|
||||||
"format_data": "{\"header\":\"<div class=\\\"document-header\\\">\\n\\t<h3>LMS Certificate</h3>\\n\\t<p>{{ doc.name }}</p>\\n</div>\",\"sections\":[{\"label\":\"\",\"columns\":[{\"label\":\"\",\"fields\":[{\"label\":\"Course\",\"fieldname\":\"course\",\"fieldtype\":\"Link\",\"options\":\"LMS Course\"},{\"label\":\"Member\",\"fieldname\":\"member\",\"fieldtype\":\"Link\",\"options\":\"User\"},{\"label\":\"Member Name\",\"fieldname\":\"member_name\",\"fieldtype\":\"Data\"},{\"label\":\"Evaluator\",\"fieldname\":\"evaluator\",\"fieldtype\":\"Data\",\"options\":\"\"}]},{\"label\":\"\",\"fields\":[{\"label\":\"Issue Date\",\"fieldname\":\"issue_date\",\"fieldtype\":\"Date\"},{\"label\":\"Expiry Date\",\"fieldname\":\"expiry_date\",\"fieldtype\":\"Date\"},{\"label\":\"Version\",\"fieldname\":\"version\",\"fieldtype\":\"Select\",\"options\":\"V13\\nV14\"},{\"label\":\"Module Names for Certificate\",\"fieldname\":\"module_names_for_certificate\",\"fieldtype\":\"Data\"}]}],\"has_fields\":true}]}",
|
"format_data": "{\"header\":\"<div class=\\\"document-header\\\">\\n\\t<h3>LMS Certificate</h3>\\n\\t<p>{{ doc.name }}</p>\\n</div>\",\"sections\":[{\"label\":\"\",\"columns\":[{\"label\":\"\",\"fields\":[{\"label\":\"Course\",\"fieldname\":\"course\",\"fieldtype\":\"Link\",\"options\":\"LMS Course\"},{\"label\":\"Member\",\"fieldname\":\"member\",\"fieldtype\":\"Link\",\"options\":\"User\"},{\"label\":\"Member Name\",\"fieldname\":\"member_name\",\"fieldtype\":\"Data\"},{\"label\":\"Evaluator\",\"fieldname\":\"evaluator\",\"fieldtype\":\"Data\",\"options\":\"\"}]},{\"label\":\"\",\"fields\":[{\"label\":\"Issue Date\",\"fieldname\":\"issue_date\",\"fieldtype\":\"Date\"},{\"label\":\"Expiry Date\",\"fieldname\":\"expiry_date\",\"fieldtype\":\"Date\"},{\"label\":\"Version\",\"fieldname\":\"version\",\"fieldtype\":\"Select\",\"options\":\"V13\\nV14\"},{\"label\":\"Module Names for Certificate\",\"fieldname\":\"module_names_for_certificate\",\"fieldtype\":\"Data\"}]}],\"has_fields\":true}]}",
|
||||||
"html": "{% set certificate = frappe.db.get_value(\"LMS Certificate\", doc.name, [\"name\", \"member\", \"issue_date\", \"expiry_date\", \"course\"], as_dict=True) %}\n{% set member = frappe.db.get_value(\"User\", doc.member, [\"full_name\"], as_dict=True) %}\n{% set course = frappe.db.get_value(\"LMS Course\", doc.course, [\"title\", \"name\", \"image\"], as_dict=True) %}\n{% set logo = frappe.db.get_single_value(\"Website Settings\", \"banner_image\") %}\n{% set instructors = frappe.get_all(\"Course Instructor\", {\"parent\": doc.course}, pluck=\"instructor\", order_by=\"idx\") %}\n\n<meta name=\"pdfkit-orientation\" content=\"Landscape\">\n<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n<link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500&display=swap\" rel=\"stylesheet\">\n\n<div class=\"outer-border\">\n <div class=\"inner-border\">\n \n {% if logo %}\n <img src=\"{{ logo }}\" class=\"certificate-logo\">\n {% endif %}\n <div>\n {{ _(\"This certifies that\") }}\n </div>\n \n <div class=\"certificate-name\" style=\"\">\n {{ member.full_name }}\n </div>\n <div>\n {{ _(\"has successfully completed the course on\") }}\n <b> {{ course.title }} </b>\n on {{ frappe.utils.format_date(certificate.issue_date, \"medium\") }}.\n </div>\n \n <table class=\"certificate-footer\">\n <tr>\n {% if instructors %}\n <td>\n <div class=\"certificate-footer-item cursive-font\">\n {% for i in instructors %}\n \t\t\t\t\t{{ frappe.db.get_value(\"User\", i, \"full_name\") }}\n \t\t\t\t\t{% if not loop.last %}\n \t\t\t\t\t,\n \t\t\t\t\t{% endif %}\n \t\t\t\t\t{% endfor %}\n </div>\n <hr class=\"certificate-divider\">\n <div class=\"text-center\"> {{ _(\"Course Instructor\") }} </div>\n </td>\n {% endif %}\n \n {% if certificate.expiry_date %}\n <td style=\"width: 30%\"></td>\n \n <td class=\"certificate-expiry\">\n <div class=\"certificate-footer-item\">\n {{ frappe.utils.format_date(certificate.expiry_date, \"medium\") }}\n </div>\n <hr class=\"certificate-divider\">\n <div class=\"text-center\"> {{ _(\"Expiry Date\") }} </div>\n </td>\n {% endif %}\n </tr>\n </table>\n </div>\n </div>",
|
"html": "{% set certificate = frappe.db.get_value(\"LMS Certificate\", doc.name, [\"name\", \"member\", \"issue_date\", \"expiry_date\", \"course\"], as_dict=True) %}\n{% set member = frappe.db.get_value(\"User\", doc.member, [\"full_name\"], as_dict=True) %}\n{% set course = frappe.db.get_value(\"LMS Course\", doc.course, [\"title\", \"name\", \"image\"], as_dict=True) %}\n{% set logo = frappe.db.get_single_value(\"Website Settings\", \"banner_image\") %}\n{% set instructors = frappe.get_all(\"Course Instructor\", {\"parent\": doc.course}, pluck=\"instructor\", order_by=\"idx\") %}\n\n<meta name=\"pdfkit-orientation\" content=\"Landscape\">\n<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n<link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500&display=swap\" rel=\"stylesheet\">\n\n<div>\n <div class=\"inner-border\">\n \n {% if logo %}\n <img src=\"{{ logo }}\" class=\"certificate-logo\">\n {% endif %}\n <div>\n {{ _(\"This certifies that\") }}\n </div>\n \n <div class=\"certificate-name\" style=\"\">\n {{ member.full_name }}\n </div>\n <div>\n {{ _(\"has successfully completed the course on\") }}\n <b> {{ course.title }} </b>\n on {{ frappe.utils.format_date(certificate.issue_date, \"medium\") }}.\n </div>\n \n <table class=\"certificate-footer\">\n <tr>\n {% if instructors %}\n <td>\n <div class=\"certificate-footer-item cursive-font\">\n {% for i in instructors %}\n \t\t\t\t\t{{ frappe.db.get_value(\"User\", i, \"full_name\") }}\n \t\t\t\t\t{% if not loop.last %}\n \t\t\t\t\t,\n \t\t\t\t\t{% endif %}\n \t\t\t\t\t{% endfor %}\n </div>\n <hr class=\"certificate-divider\">\n <div class=\"text-center\"> {{ _(\"Course Instructor\") }} </div>\n </td>\n {% endif %}\n \n {% if certificate.expiry_date %}\n <td style=\"width: 30%\"></td>\n \n <td class=\"certificate-expiry\">\n <div class=\"certificate-footer-item\">\n {{ frappe.utils.format_date(certificate.expiry_date, \"medium\") }}\n </div>\n <hr class=\"certificate-divider\">\n <div class=\"text-center\"> {{ _(\"Expiry Date\") }} </div>\n </td>\n {% endif %}\n </tr>\n </table>\n </div>\n </div>",
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"line_breaks": 0,
|
"line_breaks": 0,
|
||||||
"margin_bottom": 0.0,
|
"margin_bottom": 0.0,
|
||||||
"margin_left": 0.0,
|
"margin_left": 0.0,
|
||||||
"margin_right": 0.0,
|
"margin_right": 0.0,
|
||||||
"margin_top": 0.0,
|
"margin_top": 0.0,
|
||||||
"modified": "2023-08-09 17:02:21.430320",
|
"modified": "2023-11-01 18:22:56.715846",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "Certificate",
|
"name": "Certificate",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"page_number": "Hide",
|
"page_number": "Hide",
|
||||||
|
"print_designer": 0,
|
||||||
"print_format_builder": 0,
|
"print_format_builder": 0,
|
||||||
"print_format_builder_beta": 1,
|
"print_format_builder_beta": 1,
|
||||||
"print_format_type": "Jinja",
|
"print_format_type": "Jinja",
|
||||||
|
|||||||
@@ -72,4 +72,4 @@ def create_evaluation(user, course, date, rating, status):
|
|||||||
"status": status,
|
"status": status,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
evaluation.save(ignore_permissions=True)
|
evaluation.save()
|
||||||
|
|||||||
137
lms/lms/utils.py
137
lms/lms/utils.py
@@ -3,6 +3,7 @@ import string
|
|||||||
import frappe
|
import frappe
|
||||||
import json
|
import json
|
||||||
import razorpay
|
import razorpay
|
||||||
|
import requests
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_result
|
from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_result
|
||||||
from frappe.desk.doctype.notification_log.notification_log import make_notification_logs
|
from frappe.desk.doctype.notification_log.notification_log import make_notification_logs
|
||||||
@@ -16,6 +17,7 @@ from frappe.utils import (
|
|||||||
get_datetime,
|
get_datetime,
|
||||||
getdate,
|
getdate,
|
||||||
validate_phone_number,
|
validate_phone_number,
|
||||||
|
ceil,
|
||||||
)
|
)
|
||||||
from frappe.utils.dateutils import get_period
|
from frappe.utils.dateutils import get_period
|
||||||
from lms.lms.md import find_macros, markdown_to_html
|
from lms.lms.md import find_macros, markdown_to_html
|
||||||
@@ -143,12 +145,12 @@ def get_lesson_details(chapter):
|
|||||||
"quiz_id",
|
"quiz_id",
|
||||||
"question",
|
"question",
|
||||||
"file_type",
|
"file_type",
|
||||||
|
"instructor_notes",
|
||||||
],
|
],
|
||||||
as_dict=True,
|
as_dict=True,
|
||||||
)
|
)
|
||||||
lesson_details.number = flt(f"{chapter.idx}.{row.idx}")
|
lesson_details.number = f"{chapter.idx}.{row.idx}"
|
||||||
lesson_details.icon = get_lesson_icon(lesson_details.body)
|
lesson_details.icon = get_lesson_icon(lesson_details.body)
|
||||||
|
|
||||||
lessons.append(lesson_details)
|
lessons.append(lesson_details)
|
||||||
return lessons
|
return lessons
|
||||||
|
|
||||||
@@ -518,21 +520,35 @@ def has_course_instructor_role(member=None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def can_create_courses(member=None):
|
def can_create_courses(course, member=None):
|
||||||
if not member:
|
if not member:
|
||||||
member = frappe.session.user
|
member = frappe.session.user
|
||||||
|
|
||||||
|
instructors = frappe.get_all(
|
||||||
|
"Course Instructor",
|
||||||
|
{
|
||||||
|
"parent": course,
|
||||||
|
},
|
||||||
|
pluck="instructor",
|
||||||
|
)
|
||||||
|
|
||||||
if frappe.session.user == "Guest":
|
if frappe.session.user == "Guest":
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if has_course_instructor_role(member) or has_course_moderator_role(member):
|
if has_course_moderator_role(member):
|
||||||
|
return True
|
||||||
|
|
||||||
|
if has_course_instructor_role(member) and member in instructors:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
portal_course_creation = frappe.db.get_single_value(
|
portal_course_creation = frappe.db.get_single_value(
|
||||||
"LMS Settings", "portal_course_creation"
|
"LMS Settings", "portal_course_creation"
|
||||||
)
|
)
|
||||||
|
|
||||||
return portal_course_creation == "Anyone"
|
if portal_course_creation == "Anyone" and member in instructors:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def has_course_moderator_role(member=None):
|
def has_course_moderator_role(member=None):
|
||||||
@@ -582,7 +598,7 @@ def validate_image(path):
|
|||||||
if path and "/private" in path:
|
if path and "/private" in path:
|
||||||
file = frappe.get_doc("File", {"file_url": path})
|
file = frappe.get_doc("File", {"file_url": path})
|
||||||
file.is_private = 0
|
file.is_private = 0
|
||||||
file.save(ignore_permissions=True)
|
file.save()
|
||||||
return file.file_url
|
return file.file_url
|
||||||
return path
|
return path
|
||||||
|
|
||||||
@@ -724,7 +740,7 @@ def get_chart_data(chart_name, timespan, timegrain, from_date, to_date):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist(allow_guest=True)
|
||||||
def get_course_completion_data():
|
def get_course_completion_data():
|
||||||
all_membership = frappe.db.count("LMS Enrollment")
|
all_membership = frappe.db.count("LMS Enrollment")
|
||||||
completed = frappe.db.count("LMS Enrollment", {"progress": ["like", "%100%"]})
|
completed = frappe.db.count("LMS Enrollment", {"progress": ["like", "%100%"]})
|
||||||
@@ -830,19 +846,23 @@ def get_upcoming_evals(student, courses):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_payment_options(doctype, docname, phone):
|
def get_payment_options(doctype, docname, phone, country):
|
||||||
if not frappe.db.exists(doctype, docname):
|
if not frappe.db.exists(doctype, docname):
|
||||||
frappe.throw(_("Invalid document provided."))
|
frappe.throw(_("Invalid document provided."))
|
||||||
|
|
||||||
validate_phone_number(phone, True)
|
validate_phone_number(phone, True)
|
||||||
details = get_details(doctype, docname)
|
details = get_details(doctype, docname)
|
||||||
|
details.amount, details.currency = check_multicurrency(
|
||||||
|
details.amount, details.currency, country
|
||||||
|
)
|
||||||
|
if details.currency == "INR":
|
||||||
|
details.amount, details.gst_applied = apply_gst(details.amount, country)
|
||||||
|
|
||||||
razorpay_key = frappe.db.get_single_value("LMS Settings", "razorpay_key")
|
|
||||||
client = get_client()
|
client = get_client()
|
||||||
order = create_order(client, details.amount, details.currency)
|
order = create_order(client, details.amount, details.currency)
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
"key_id": razorpay_key,
|
"key_id": frappe.db.get_single_value("LMS Settings", "razorpay_key"),
|
||||||
"name": frappe.db.get_single_value("Website Settings", "app_name"),
|
"name": frappe.db.get_single_value("Website Settings", "app_name"),
|
||||||
"description": _("Payment for {0} course").format(details["title"]),
|
"description": _("Payment for {0} course").format(details["title"]),
|
||||||
"order_id": order["id"],
|
"order_id": order["id"],
|
||||||
@@ -857,6 +877,46 @@ def get_payment_options(doctype, docname, phone):
|
|||||||
return options
|
return options
|
||||||
|
|
||||||
|
|
||||||
|
def check_multicurrency(amount, currency, country=None):
|
||||||
|
show_usd_equivalent = frappe.db.get_single_value("LMS Settings", "show_usd_equivalent")
|
||||||
|
exception_country = frappe.get_all(
|
||||||
|
"Payment Country", filters={"parent": "LMS Settings"}, pluck="country"
|
||||||
|
)
|
||||||
|
apply_rounding = frappe.db.get_single_value("LMS Settings", "apply_rounding")
|
||||||
|
country = country or frappe.db.get_value(
|
||||||
|
"Address", {"email_id": frappe.session.user}, "country"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not show_usd_equivalent or currency == "USD":
|
||||||
|
return amount, currency
|
||||||
|
|
||||||
|
if not country or (exception_country and country in exception_country):
|
||||||
|
return amount, currency
|
||||||
|
|
||||||
|
exchange_rate = get_current_exchange_rate(currency, "USD")
|
||||||
|
amount = amount * exchange_rate
|
||||||
|
currency = "USD"
|
||||||
|
|
||||||
|
if apply_rounding and amount % 100 != 0:
|
||||||
|
amount = amount + 100 - amount % 100
|
||||||
|
|
||||||
|
return amount, currency
|
||||||
|
|
||||||
|
|
||||||
|
def apply_gst(amount, country=None):
|
||||||
|
gst_applied = False
|
||||||
|
apply_gst = frappe.db.get_single_value("LMS Settings", "apply_gst")
|
||||||
|
|
||||||
|
if not country:
|
||||||
|
country = frappe.db.get_value("User", frappe.session.user, "country")
|
||||||
|
|
||||||
|
if apply_gst and country == "India":
|
||||||
|
gst_applied = True
|
||||||
|
amount = amount * 1.18
|
||||||
|
|
||||||
|
return amount, gst_applied
|
||||||
|
|
||||||
|
|
||||||
def get_details(doctype, docname):
|
def get_details(doctype, docname):
|
||||||
if doctype == "LMS Course":
|
if doctype == "LMS Course":
|
||||||
details = frappe.db.get_value(
|
details = frappe.db.get_value(
|
||||||
@@ -881,7 +941,15 @@ def get_details(doctype, docname):
|
|||||||
|
|
||||||
|
|
||||||
def save_address(address):
|
def save_address(address):
|
||||||
address.update(
|
filters = {"email_id": frappe.session.user}
|
||||||
|
exists = frappe.db.exists("Address", filters)
|
||||||
|
if exists:
|
||||||
|
address_doc = frappe.get_last_doc("Address", filters=filters)
|
||||||
|
else:
|
||||||
|
address_doc = frappe.new_doc("Address")
|
||||||
|
|
||||||
|
address_doc.update(address)
|
||||||
|
address_doc.update(
|
||||||
{
|
{
|
||||||
"address_title": frappe.db.get_value("User", frappe.session.user, "full_name"),
|
"address_title": frappe.db.get_value("User", frappe.session.user, "full_name"),
|
||||||
"address_type": "Billing",
|
"address_type": "Billing",
|
||||||
@@ -889,15 +957,14 @@ def save_address(address):
|
|||||||
"email_id": frappe.session.user,
|
"email_id": frappe.session.user,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
doc = frappe.new_doc("Address")
|
address_doc.save(ignore_permissions=True)
|
||||||
doc.update(address)
|
return address_doc.name
|
||||||
doc.save(ignore_permissions=True)
|
|
||||||
return doc.name
|
|
||||||
|
|
||||||
|
|
||||||
def get_client():
|
def get_client():
|
||||||
razorpay_key = frappe.db.get_single_value("LMS Settings", "razorpay_key")
|
settings = frappe.get_single("LMS Settings")
|
||||||
razorpay_secret = frappe.db.get_single_value("LMS Settings", "razorpay_secret")
|
razorpay_key = settings.razorpay_key
|
||||||
|
razorpay_secret = settings.get_password("razorpay_secret", raise_exception=True)
|
||||||
|
|
||||||
if not razorpay_key and not razorpay_secret:
|
if not razorpay_key and not razorpay_secret:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
@@ -946,7 +1013,7 @@ def record_payment(address, response, client, doctype, docname):
|
|||||||
address = frappe._dict(json.loads(address))
|
address = frappe._dict(json.loads(address))
|
||||||
address_name = save_address(address)
|
address_name = save_address(address)
|
||||||
|
|
||||||
payment_details = get_payment_details(doctype, docname)
|
payment_details = get_payment_details(doctype, docname, address)
|
||||||
payment_doc = frappe.new_doc("LMS Payment")
|
payment_doc = frappe.new_doc("LMS Payment")
|
||||||
payment_doc.update(
|
payment_doc.update(
|
||||||
{
|
{
|
||||||
@@ -958,29 +1025,39 @@ def record_payment(address, response, client, doctype, docname):
|
|||||||
"payment_id": response["razorpay_payment_id"],
|
"payment_id": response["razorpay_payment_id"],
|
||||||
"amount": payment_details["amount"],
|
"amount": payment_details["amount"],
|
||||||
"currency": payment_details["currency"],
|
"currency": payment_details["currency"],
|
||||||
|
"amount_with_gst": payment_details["amount_with_gst"],
|
||||||
"gstin": address.gstin,
|
"gstin": address.gstin,
|
||||||
"pan": address.pan,
|
"pan": address.pan,
|
||||||
|
"source": address.source,
|
||||||
|
"payment_for_document_type": doctype,
|
||||||
|
"payment_for_document": docname,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
payment_doc.save(ignore_permissions=True)
|
payment_doc.save(ignore_permissions=True)
|
||||||
return payment_doc.name
|
return payment_doc
|
||||||
|
|
||||||
|
|
||||||
def get_payment_details(doctype, docname):
|
def get_payment_details(doctype, docname, address):
|
||||||
amount_field = "course_price" if doctype == "LMS Course" else "amount"
|
amount_field = "course_price" if doctype == "LMS Course" else "amount"
|
||||||
amount = frappe.db.get_value(doctype, docname, amount_field)
|
amount = frappe.db.get_value(doctype, docname, amount_field)
|
||||||
currency = frappe.db.get_value(doctype, docname, "currency")
|
currency = frappe.db.get_value(doctype, docname, "currency")
|
||||||
|
amount_with_gst = 0
|
||||||
|
|
||||||
|
amount, currency = check_multicurrency(amount, currency)
|
||||||
|
if currency == "INR" and address.country == "India":
|
||||||
|
amount_with_gst, gst_applied = apply_gst(amount, address.country)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"amount": amount,
|
"amount": amount,
|
||||||
"currency": currency,
|
"currency": currency,
|
||||||
|
"amount_with_gst": amount_with_gst,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def create_membership(course, payment):
|
def create_membership(course, payment):
|
||||||
membership = frappe.new_doc("LMS Enrollment")
|
membership = frappe.new_doc("LMS Enrollment")
|
||||||
membership.update(
|
membership.update(
|
||||||
{"member": frappe.session.user, "course": course, "payment": payment}
|
{"member": frappe.session.user, "course": course, "payment": payment.name}
|
||||||
)
|
)
|
||||||
membership.save(ignore_permissions=True)
|
membership.save(ignore_permissions=True)
|
||||||
return f"/courses/{course}/learn/1.1"
|
return f"/courses/{course}/learn/1.1"
|
||||||
@@ -991,7 +1068,8 @@ def add_student_to_batch(batchname, payment):
|
|||||||
student.update(
|
student.update(
|
||||||
{
|
{
|
||||||
"student": frappe.session.user,
|
"student": frappe.session.user,
|
||||||
"payment": payment,
|
"payment": payment.name,
|
||||||
|
"source": payment.source,
|
||||||
"parent": batchname,
|
"parent": batchname,
|
||||||
"parenttype": "LMS Batch",
|
"parenttype": "LMS Batch",
|
||||||
"parentfield": "students",
|
"parentfield": "students",
|
||||||
@@ -999,3 +1077,18 @@ def add_student_to_batch(batchname, payment):
|
|||||||
)
|
)
|
||||||
student.save(ignore_permissions=True)
|
student.save(ignore_permissions=True)
|
||||||
return f"/batches/{batchname}"
|
return f"/batches/{batchname}"
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_exchange_rate(source, target="USD"):
|
||||||
|
url = f"https://api.frankfurter.app/latest?from={source}&to={target}"
|
||||||
|
|
||||||
|
response = requests.request("GET", url)
|
||||||
|
details = response.json()
|
||||||
|
return details["rates"][target]
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def change_currency(amount, currency, country=None):
|
||||||
|
amount = cint(amount)
|
||||||
|
amount, currency = check_multicurrency(amount, currency, country)
|
||||||
|
return fmt_money(amount, 0, currency)
|
||||||
|
|||||||
@@ -235,13 +235,15 @@ def sign_up(email, full_name, verify_terms, user_category):
|
|||||||
user.flags.ignore_permissions = True
|
user.flags.ignore_permissions = True
|
||||||
user.flags.ignore_password_policy = True
|
user.flags.ignore_password_policy = True
|
||||||
user.insert()
|
user.insert()
|
||||||
set_country_from_ip(None, user.name)
|
|
||||||
|
|
||||||
# set default signup role as per Portal Settings
|
# set default signup role as per Portal Settings
|
||||||
default_role = frappe.db.get_value("Portal Settings", None, "default_role")
|
default_role = frappe.db.get_value("Portal Settings", None, "default_role")
|
||||||
if default_role:
|
if default_role:
|
||||||
user.add_roles(default_role)
|
user.add_roles(default_role)
|
||||||
|
|
||||||
|
user.add_roles("LMS Student")
|
||||||
|
set_country_from_ip(None, user.name)
|
||||||
|
|
||||||
if user.flags.email_sent:
|
if user.flags.email_sent:
|
||||||
return 1, _("Please check your email for verification")
|
return 1, _("Please check your email for verification")
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
[pre_model_sync]
|
||||||
community.patches.set_email_preferences
|
community.patches.set_email_preferences
|
||||||
community.patches.change_name_for_community_members
|
community.patches.change_name_for_community_members
|
||||||
community.patches.save_abbr_for_community_members
|
community.patches.save_abbr_for_community_members
|
||||||
@@ -67,3 +68,17 @@ lms.patches.v1_0.rename_lms_batch_doctype
|
|||||||
lms.patches.v1_0.rename_lms_batch_membership_doctype
|
lms.patches.v1_0.rename_lms_batch_membership_doctype
|
||||||
lms.patches.v1_0.rename_lms_class_to_lms_batch
|
lms.patches.v1_0.rename_lms_class_to_lms_batch
|
||||||
lms.patches.v1_0.rename_classes_in_navbar
|
lms.patches.v1_0.rename_classes_in_navbar
|
||||||
|
lms.patches.v1_0.publish_batches
|
||||||
|
lms.patches.v1_0.publish_certificates
|
||||||
|
lms.patches.v1_0.change_naming_for_batch_course #14-09-2023
|
||||||
|
execute:frappe.permissions.reset_perms("LMS Enrollment")
|
||||||
|
lms.patches.v1_0.create_student_role
|
||||||
|
lms.patches.v1_0.mark_confirmation_for_batch_students
|
||||||
|
lms.patches.v1_0.create_quiz_questions
|
||||||
|
lms.patches.v1_0.add_default_marks #16-10-2023
|
||||||
|
lms.patches.v1_0.add_certificate_template #26-10-2023
|
||||||
|
lms.patches.v1_0.create_batch_source
|
||||||
|
|
||||||
|
[post_model_sync]
|
||||||
|
lms.patches.v1_0.batch_tabs_settings
|
||||||
|
execute:frappe.delete_doc("Notification", "Assignment Submission Notification")
|
||||||
20
lms/patches/v1_0/add_certificate_template.py
Normal file
20
lms/patches/v1_0/add_certificate_template.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
frappe.reload_doc("lms", "doctype", "lms_certificate")
|
||||||
|
default_certificate_template = frappe.db.get_value(
|
||||||
|
"Property Setter",
|
||||||
|
{
|
||||||
|
"doc_type": "LMS Certificate",
|
||||||
|
"property": "default_print_format",
|
||||||
|
},
|
||||||
|
"value",
|
||||||
|
)
|
||||||
|
|
||||||
|
if frappe.db.exists("Print Format", default_certificate_template):
|
||||||
|
certificates = frappe.get_all("LMS Certificate", pluck="name")
|
||||||
|
for certificate in certificates:
|
||||||
|
frappe.db.set_value(
|
||||||
|
"LMS Certificate", certificate, "template", default_certificate_template
|
||||||
|
)
|
||||||
18
lms/patches/v1_0/add_default_marks.py
Normal file
18
lms/patches/v1_0/add_default_marks.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
frappe.reload_doc("lms", "doctype", "lms_quiz_question")
|
||||||
|
frappe.reload_doc("lms", "doctype", "lms_quiz")
|
||||||
|
questions = frappe.get_all("LMS Quiz Question", pluck="name")
|
||||||
|
|
||||||
|
for question in questions:
|
||||||
|
frappe.db.set_value("LMS Quiz Question", question, "marks", 1)
|
||||||
|
|
||||||
|
quizzes = frappe.get_all("LMS Quiz", pluck="name")
|
||||||
|
|
||||||
|
for quiz in quizzes:
|
||||||
|
questions_count = frappe.db.count("LMS Quiz Question", {"parent": quiz})
|
||||||
|
frappe.db.set_value(
|
||||||
|
"LMS Quiz", quiz, {"total_marks": questions_count, "passing_percentage": 100}
|
||||||
|
)
|
||||||
16
lms/patches/v1_0/batch_tabs_settings.py
Normal file
16
lms/patches/v1_0/batch_tabs_settings.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
fields = [
|
||||||
|
"show_dashboard",
|
||||||
|
"show_courses",
|
||||||
|
"show_students",
|
||||||
|
"show_emails",
|
||||||
|
"show_assessments",
|
||||||
|
"show_discussions",
|
||||||
|
"show_live_class",
|
||||||
|
]
|
||||||
|
|
||||||
|
for field in fields:
|
||||||
|
frappe.db.set_single_value("LMS Settings", field, 1)
|
||||||
6
lms/patches/v1_0/change_naming_for_batch_course.py
Normal file
6
lms/patches/v1_0/change_naming_for_batch_course.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
frappe.db.create_sequence("Batch Course", check_not_exists=True)
|
||||||
|
frappe.db.set_next_sequence_val("Batch Course", 500, is_val_used=False)
|
||||||
7
lms/patches/v1_0/create_batch_source.py
Normal file
7
lms/patches/v1_0/create_batch_source.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import frappe
|
||||||
|
from lms.install import create_batch_source
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
frappe.reload_doc("lms", "doctype", "lms_source")
|
||||||
|
create_batch_source()
|
||||||
43
lms/patches/v1_0/create_quiz_questions.py
Normal file
43
lms/patches/v1_0/create_quiz_questions.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
frappe.reload_doc("lms", "doctype", "lms_question")
|
||||||
|
|
||||||
|
fields = ["name", "question", "type", "multiple"]
|
||||||
|
for num in range(1, 5):
|
||||||
|
fields.append(f"option_{num}")
|
||||||
|
fields.append(f"is_correct_{num}")
|
||||||
|
fields.append(f"explanation_{num}")
|
||||||
|
fields.append(f"possibility_{num}")
|
||||||
|
|
||||||
|
questions = frappe.get_all(
|
||||||
|
"LMS Quiz Question",
|
||||||
|
fields=fields,
|
||||||
|
)
|
||||||
|
|
||||||
|
for question in questions:
|
||||||
|
print(question.name)
|
||||||
|
doc = frappe.new_doc("LMS Question")
|
||||||
|
doc.update(
|
||||||
|
{
|
||||||
|
"question": question.question,
|
||||||
|
"type": question.type,
|
||||||
|
"multiple": question.multiple,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
for num in range(1, 5):
|
||||||
|
if question.get(f"option_{num}"):
|
||||||
|
doc.update(
|
||||||
|
{
|
||||||
|
f"option_{num}": question[f"option_{num}"],
|
||||||
|
f"is_correct_{num}": question[f"is_correct_{num}"],
|
||||||
|
f"explanation_{num}": question[f"explanation_{num}"],
|
||||||
|
f"possibility_{num}": question[f"possibility_{num}"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
doc.save()
|
||||||
|
print(doc.name)
|
||||||
|
frappe.db.set_value("LMS Quiz Question", question.name, "question", doc.name)
|
||||||
22
lms/patches/v1_0/create_student_role.py
Normal file
22
lms/patches/v1_0/create_student_role.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import frappe
|
||||||
|
from lms.install import create_lms_student_role
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
create_lms_student_role()
|
||||||
|
|
||||||
|
users = frappe.get_all(
|
||||||
|
"User", filters={"user_type": "Website User", "enabled": 1}, pluck="name"
|
||||||
|
)
|
||||||
|
|
||||||
|
for user in users:
|
||||||
|
filters = {
|
||||||
|
"parent": user,
|
||||||
|
"parenttype": "User",
|
||||||
|
"parentfield": "roles",
|
||||||
|
"role": "LMS Student",
|
||||||
|
}
|
||||||
|
if not frappe.db.exists("Has Role", filters):
|
||||||
|
doc = frappe.new_doc("Has Role")
|
||||||
|
doc.update(filters)
|
||||||
|
doc.save()
|
||||||
9
lms/patches/v1_0/mark_confirmation_for_batch_students.py
Normal file
9
lms/patches/v1_0/mark_confirmation_for_batch_students.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
frappe.reload_doc("lms", "doctype", "batch_student")
|
||||||
|
students = frappe.get_all("Batch Student", pluck="name")
|
||||||
|
|
||||||
|
for student in students:
|
||||||
|
frappe.db.set_value("Batch Student", student, "confirmation_email_sent", 1)
|
||||||
9
lms/patches/v1_0/publish_batches.py
Normal file
9
lms/patches/v1_0/publish_batches.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
frappe.reload_doc("lms", "doctype", "lms_batch")
|
||||||
|
batches = frappe.get_all("LMS Batch", pluck="name")
|
||||||
|
|
||||||
|
for batch in batches:
|
||||||
|
frappe.db.set_value("LMS Batch", batch, "Published", 1)
|
||||||
9
lms/patches/v1_0/publish_certificates.py
Normal file
9
lms/patches/v1_0/publish_certificates.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
frappe.reload_doc("lms", "doctype", "lms_certificate")
|
||||||
|
certificates = frappe.get_all("LMS Certificate", pluck="name")
|
||||||
|
|
||||||
|
for certificate in certificates:
|
||||||
|
frappe.db.set_value("LMS Certificate", certificate, "published", 1)
|
||||||
@@ -109,7 +109,39 @@ def quiz_renderer(quiz_name):
|
|||||||
)
|
)
|
||||||
+"</div>"
|
+"</div>"
|
||||||
|
|
||||||
quiz = frappe.get_doc("LMS Quiz", quiz_name)
|
quiz = frappe.db.get_value(
|
||||||
|
"LMS Quiz",
|
||||||
|
quiz_name,
|
||||||
|
[
|
||||||
|
"name",
|
||||||
|
"title",
|
||||||
|
"max_attempts",
|
||||||
|
"show_answers",
|
||||||
|
"show_submission_history",
|
||||||
|
"passing_percentage",
|
||||||
|
],
|
||||||
|
as_dict=True,
|
||||||
|
)
|
||||||
|
quiz.questions = []
|
||||||
|
fields = ["name", "question", "type", "multiple"]
|
||||||
|
for num in range(1, 5):
|
||||||
|
fields.append(f"option_{num}")
|
||||||
|
fields.append(f"is_correct_{num}")
|
||||||
|
fields.append(f"explanation_{num}")
|
||||||
|
fields.append(f"possibility_{num}")
|
||||||
|
|
||||||
|
questions = frappe.get_all(
|
||||||
|
"LMS Quiz Question",
|
||||||
|
filters={"parent": quiz.name},
|
||||||
|
fields=["question", "marks"],
|
||||||
|
order_by="idx",
|
||||||
|
)
|
||||||
|
|
||||||
|
for question in questions:
|
||||||
|
details = frappe.db.get_value("LMS Question", question.question, fields, as_dict=1)
|
||||||
|
details["marks"] = question.marks
|
||||||
|
quiz.questions.append(details)
|
||||||
|
|
||||||
no_of_attempts = frappe.db.count(
|
no_of_attempts = frappe.db.count(
|
||||||
"LMS Quiz Submission", {"owner": frappe.session.user, "quiz": quiz_name}
|
"LMS Quiz Submission", {"owner": frappe.session.user, "quiz": quiz_name}
|
||||||
)
|
)
|
||||||
@@ -155,10 +187,38 @@ def youtube_video_renderer(video_id):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def embed_renderer(details):
|
||||||
|
type = details.split("|||")[0]
|
||||||
|
src = details.split("|||")[1]
|
||||||
|
width = "100%"
|
||||||
|
height = "400"
|
||||||
|
|
||||||
|
if type == "pdf":
|
||||||
|
width = "75%"
|
||||||
|
height = "600"
|
||||||
|
|
||||||
|
return f"""
|
||||||
|
<iframe width={width} height={height}
|
||||||
|
src={src}
|
||||||
|
title="Embedded Content"
|
||||||
|
frameborder="0"
|
||||||
|
style="border-radius: var(--border-radius-lg)"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||||
|
allowfullscreen>
|
||||||
|
</iframe>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def video_renderer(src):
|
def video_renderer(src):
|
||||||
return (
|
return f"<video controls width='100%' controls controlsList='nodownload'><source src={quote(src)} type='video/mp4'></video>"
|
||||||
f"<video controls width='100%'><source src={quote(src)} type='video/mp4'></video>"
|
|
||||||
)
|
|
||||||
|
def audio_renderer(src):
|
||||||
|
return f"<audio width='100%' controls controlsList='nodownload'><source src={quote(src)} type='audio/mp3'></audio>"
|
||||||
|
|
||||||
|
|
||||||
|
def pdf_renderer(src):
|
||||||
|
return f"<iframe src='{quote(src)}#toolbar=0' width='100%' height='700px'></iframe>"
|
||||||
|
|
||||||
|
|
||||||
def assignment_renderer(detail):
|
def assignment_renderer(detail):
|
||||||
|
|||||||
@@ -160,6 +160,10 @@ textarea.field-input {
|
|||||||
position: unset;
|
position: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.codex-editor--narrow .ce-toolbar__actions {
|
||||||
|
right: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.lesson-editor {
|
.lesson-editor {
|
||||||
border: 1px solid var(--gray-300);
|
border: 1px solid var(--gray-300);
|
||||||
border-radius: var(--border-radius-md);
|
border-radius: var(--border-radius-md);
|
||||||
@@ -787,6 +791,7 @@ input[type=checkbox] {
|
|||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
color: var(--gray-900);
|
color: var(--gray-900);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.course-details-outline {
|
.course-details-outline {
|
||||||
@@ -2053,8 +2058,8 @@ select {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.onboarding-parent {
|
.onboarding-parent {
|
||||||
background-color: var(--primary-light);
|
background-color: var(--gray-100);
|
||||||
padding: 2rem 0;
|
padding: 1rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.onboarding-steps {
|
.onboarding-steps {
|
||||||
@@ -2340,3 +2345,141 @@ select {
|
|||||||
.batch-course-list .cards-parent {
|
.batch-course-list .cards-parent {
|
||||||
row-gap: 3rem
|
row-gap: 3rem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.embed-tool__caption {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-buttons {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
top: 10%;
|
||||||
|
left: 80%;
|
||||||
|
z-index: 10;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastui-calendar-milestone {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastui-calendar-task {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastui-calendar-panel-resizer {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastui-calendar-day-name__date {
|
||||||
|
font-size: var(--text-base) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastui-calendar-day-name__name {
|
||||||
|
font-size: var(--text-base) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastui-calendar-day-view-day-names, .toastui-calendar-week-view-day-names {
|
||||||
|
border-bottom: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastui-calendar-layout {
|
||||||
|
border: 1px solid var(--gray-200) !important;
|
||||||
|
border-radius: var(--border-radius-md) !important;
|
||||||
|
background-color: var(--gray-100) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastui-calendar-panel .toastui-calendar-day-names.toastui-calendar-week {
|
||||||
|
border-top: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastui-calendar-panel.toastui-calendar-time {
|
||||||
|
height: 80% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastui-calendar-panel.toastui-calendar-week-view-day-names {
|
||||||
|
background-color: var(--gray-50) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastui-calendar-allday {
|
||||||
|
border-bottom: 1px solid var(--gray-200) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-navigation {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-range {
|
||||||
|
margin: 0 2rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-event-title {
|
||||||
|
font-size: var(--text-md);
|
||||||
|
font-weight: 500;
|
||||||
|
margin-top: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-color {
|
||||||
|
width: 50px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
margin-right: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-text {
|
||||||
|
color: var(--text-color);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-legends {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 50%;
|
||||||
|
margin: 0 auto 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.batch-details {
|
||||||
|
width: 50%;
|
||||||
|
margin: 2rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1000px) {
|
||||||
|
.batch-details {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-section {
|
||||||
|
font-size: var(--text-lg);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-section.collapsed .icon {
|
||||||
|
transition: all 0.5s;
|
||||||
|
-webkit-transform: rotate(180deg);
|
||||||
|
-moz-transform: rotate(180deg);
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body .ql-container {
|
||||||
|
max-height: unset !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.questions-table .row-index {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-color {
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
@@ -261,6 +261,24 @@ const open_batch_dialog = () => {
|
|||||||
reqd: 1,
|
reqd: 1,
|
||||||
default: batch_info && batch_info.title,
|
default: batch_info && batch_info.title,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
fieldtype: "Check",
|
||||||
|
label: __("Published"),
|
||||||
|
fieldname: "published",
|
||||||
|
default: batch_info && batch_info.published,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: "Column Break",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: "Int",
|
||||||
|
label: __("Seat Count"),
|
||||||
|
fieldname: "seat_count",
|
||||||
|
default: batch_info && batch_info.seat_count,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: "Section Break",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
fieldtype: "Date",
|
fieldtype: "Date",
|
||||||
label: __("Start Date"),
|
label: __("Start Date"),
|
||||||
@@ -297,17 +315,12 @@ const open_batch_dialog = () => {
|
|||||||
fieldname: "end_time",
|
fieldname: "end_time",
|
||||||
default: batch_info && batch_info.end_time,
|
default: batch_info && batch_info.end_time,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
fieldtype: "Int",
|
|
||||||
label: __("Seat Count"),
|
|
||||||
fieldname: "seat_count",
|
|
||||||
default: batch_info && batch_info.seat_count,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
fieldtype: "Link",
|
fieldtype: "Link",
|
||||||
label: __("Category"),
|
label: __("Category"),
|
||||||
fieldname: "category",
|
fieldname: "category",
|
||||||
options: "LMS Category",
|
options: "LMS Category",
|
||||||
|
only_select: 1,
|
||||||
default: batch_info && batch_info.category,
|
default: batch_info && batch_info.category,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -327,6 +340,18 @@ const open_batch_dialog = () => {
|
|||||||
default: batch_info && batch_info.batch_details,
|
default: batch_info && batch_info.batch_details,
|
||||||
reqd: 1,
|
reqd: 1,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
fieldtype: "HTML Editor",
|
||||||
|
label: __("Batch Details Raw"),
|
||||||
|
fieldname: "batch_details_raw",
|
||||||
|
default: batch_info && batch_info.batch_details_raw,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: "Attach Image",
|
||||||
|
label: __("Meta Image"),
|
||||||
|
fieldname: "meta_image",
|
||||||
|
default: batch_info && batch_info.meta_image,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
fieldtype: "Section Break",
|
fieldtype: "Section Break",
|
||||||
label: __("Pricing"),
|
label: __("Pricing"),
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
{% set certificates = get_certificates(user) %}
|
|
||||||
|
|
||||||
{% if certificates | length %}
|
{% if certificates | length %}
|
||||||
<div class="cards-parent">
|
<div class="cards-parent">
|
||||||
{% for certificate in certificates %}
|
{% for certificate in certificates %}
|
||||||
|
|||||||
10
lms/templates/emails/assignment_submission.html
Normal file
10
lms/templates/emails/assignment_submission.html
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<p>
|
||||||
|
{{ _("{0} has submitted the assignment {1}").format(frappe.bold(member_name), frappe.bold(assignment_title)) }}
|
||||||
|
</p>
|
||||||
|
<br>
|
||||||
|
<p> {{ _(" Please evaluate and grade it.") }} </p>
|
||||||
|
<br>`
|
||||||
|
<a href="/assignment-submission/{{ assignment_name }}/{{ submission_name }}">
|
||||||
|
{{ _("Open Assignment") }}
|
||||||
|
</a>
|
||||||
|
|
||||||
38
lms/templates/emails/batch_confirmation.html
Normal file
38
lms/templates/emails/batch_confirmation.html
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<p>
|
||||||
|
{{ _("Dear ") }} {{ student_name }},
|
||||||
|
</p>
|
||||||
|
<br>
|
||||||
|
<p>
|
||||||
|
{{ _("I am pleased to inform you that your enrollment for the upcoming training batch has been successfully processed. Congratulations!") }}
|
||||||
|
</p>
|
||||||
|
<br>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
{{ _("Important Details:") }}
|
||||||
|
</b>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<b>{{ _("Batch Start Date:") }}</b> {{ frappe.utils.format_date(start_date, "medium") }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<b>{{ _("Medium:") }}</b> {{ medium }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<b>{{ _("Timings:") }}</b> {{ frappe.utils.format_time(start_time, "hh:mm a") }}
|
||||||
|
</p>
|
||||||
|
<br>
|
||||||
|
<p>
|
||||||
|
{{ _("Visit the following link to view your ") }}
|
||||||
|
<a href="/batches/{{ name }}">{{ _("Batch Details") }}</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{ _("If you have any questions or require assistance, feel free to contact us.") }}
|
||||||
|
</p>
|
||||||
|
<br>
|
||||||
|
<p>
|
||||||
|
{{ _("Best Regards") }}
|
||||||
|
</p>
|
||||||
|
|
||||||
21
lms/templates/emails/certification.html
Normal file
21
lms/templates/emails/certification.html
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<p>
|
||||||
|
{{ _("Dear ") }} {{ student_name }},
|
||||||
|
</p>
|
||||||
|
<br>
|
||||||
|
<p>
|
||||||
|
{{ _("I am delighted to inform you that you have successfully earned your certification for the {0} course. Congratulations!").format(frappe.bold(course_title)) }}
|
||||||
|
</p>
|
||||||
|
<br>
|
||||||
|
<p>
|
||||||
|
{{ _("With this certification, you can now showcase your updated skills and share your achievement with your colleagues and on LinkedIn. To access your certificate, please click on the link provided below.") }}
|
||||||
|
</p>
|
||||||
|
<br>
|
||||||
|
<a href="/courses/{{ course_name }}/{{certificate_name}}">{{ _("Certificate Link") }}</a>
|
||||||
|
<br>
|
||||||
|
<p>
|
||||||
|
{{ _("Once again, congratulations on this significant accomplishment.")}}
|
||||||
|
</p>
|
||||||
|
<br>
|
||||||
|
<p>
|
||||||
|
{{ _("Best Regards") }}
|
||||||
|
</p>
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<div class="onboarding-parent">
|
<div class="onboarding-parent">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="onboarding-skip">{{ _("Skip") }}</div>
|
<div class="onboarding-skip">{{ _("Skip") }}</div>
|
||||||
<div class="course-home-headings mt-0">
|
<div class="page-title">
|
||||||
{{ _("Get Started") }}
|
{{ _("Get Started") }}
|
||||||
</div>
|
</div>
|
||||||
<div class="onboarding-subtitle">
|
<div class="onboarding-subtitle">
|
||||||
|
|||||||
@@ -6,6 +6,15 @@
|
|||||||
{{ _("This quiz consists of {0} questions.").format(quiz.questions | length) }}
|
{{ _("This quiz consists of {0} questions.").format(quiz.questions | length) }}
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
{% if quiz.passing_percentage %}
|
||||||
|
<li>
|
||||||
|
{{ _("You will have to get {0}% correct answers in order to pass the quiz.").format(quiz.passing_percentage) }}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
{{ _("Without passing the quiz you won't be able to complete the lesson.") }}
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if quiz.max_attempts %}
|
{% if quiz.max_attempts %}
|
||||||
{% set suffix = "times" if quiz.max_attempts > 1 else "time" %}
|
{% set suffix = "times" if quiz.max_attempts > 1 else "time" %}
|
||||||
<li>
|
<li>
|
||||||
@@ -20,7 +29,6 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
<div id="start-banner" class="common-card-style column-card align-items-center">
|
<div id="start-banner" class="common-card-style column-card align-items-center">
|
||||||
|
|
||||||
<div class="text-center my-10">
|
<div class="text-center my-10">
|
||||||
@@ -50,8 +58,12 @@
|
|||||||
<div class="question hide" data-name="{{ question.name }}" data-type="{{ question.type }}"
|
<div class="question hide" data-name="{{ question.name }}" data-type="{{ question.type }}"
|
||||||
data-multi="{{ question.multiple }}" data-qt-index="{{ loop.index }}">
|
data-multi="{{ question.multiple }}" data-qt-index="{{ loop.index }}">
|
||||||
<div>
|
<div>
|
||||||
|
<div class="pull-right font-weight-bold">
|
||||||
|
{{ question.marks }} {{ _("Marks") }}
|
||||||
|
</div>
|
||||||
<div class="question-number">
|
<div class="question-number">
|
||||||
{{ _("Question ") }}{{ loop.index }}: {{ instruction }}</div>
|
{{ _("Question ") }}{{ loop.index }}: {{ instruction }}
|
||||||
|
</div>
|
||||||
<div class="question-text">
|
<div class="question-text">
|
||||||
{{ question.question }}
|
{{ question.question }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user