diff --git a/.eslintrc b/.eslintrc
index c5e7d68..63f2ba5 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -26,6 +26,7 @@
"root": true,
"globals": {
"frappe": true,
+ "tinymce": true,
"Vue": true,
"SetVueGlobals": true,
"__": true,
diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml
new file mode 100644
index 0000000..ef9ba0c
--- /dev/null
+++ b/.github/workflows/linters.yml
@@ -0,0 +1,99 @@
+name: Linters
+
+on:
+ pull_request:
+ branches: [staging, test-production, version-15]
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+concurrency:
+ group: linters-one_lms-${{ github.event_name }}-${{ github.event.number || github.run_id }}
+ cancel-in-progress: true
+
+jobs:
+ commit-lint:
+ name: 'Semantic Commits'
+ runs-on: ubuntu-latest
+ if: github.event_name == 'pull_request'
+
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 200
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 18
+ check-latest: true
+
+ - name: Verify commitlint config exists
+ run: |
+ if [ ! -f "commitlint.config.js" ]; then
+ echo "Error: commitlint.config.js not found"
+ exit 1
+ fi
+
+ - name: Check commit titles
+ run: |
+ npm install @commitlint/cli @commitlint/config-conventional
+ npx commitlint --verbose --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }}
+
+ linter:
+ name: 'Semgrep Rules'
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-python@v4
+ with:
+ python-version: '3.11'
+ cache: pip
+
+ - name: Download Semgrep rules
+ run: git clone --depth 1 --branch develop https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules
+
+ - name: Run Semgrep rules
+ run: |
+ pip install semgrep
+ semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness
+
+ deps-vulnerable-check:
+ name: 'Vulnerable Dependency Check'
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/setup-python@v4
+ with:
+ python-version: '3.11'
+
+ - uses: actions/checkout@v4
+
+ - name: Cache pip
+ uses: actions/cache@v3
+ with:
+ path: ~/.cache/pip
+ key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py') }}
+ restore-keys: |
+ ${{ runner.os }}-pip-
+ ${{ runner.os }}-
+
+ - name: Install and run pip-audit
+ run: |
+ pip install pip-audit
+ cd ${GITHUB_WORKSPACE}
+ pip-audit --desc on .
+
+ precommit:
+ name: 'Pre-commit Hooks'
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-python@v4
+ with:
+ python-version: '3.11'
+
+ - name: Run pre-commit
+ uses: pre-commit/action@v3.0.1
diff --git a/commitlint.config.js b/commitlint.config.js
new file mode 100644
index 0000000..26a88cb
--- /dev/null
+++ b/commitlint.config.js
@@ -0,0 +1,3 @@
+module.exports = {
+ extends: ["@commitlint/config-conventional"],
+};
diff --git a/one_lms/__init__.py b/one_lms/__init__.py
index ea124cb..70797cf 100644
--- a/one_lms/__init__.py
+++ b/one_lms/__init__.py
@@ -1,7 +1,8 @@
-from one_lms.overrides.plugins import assignment_renderer, quiz_renderer
from lms import plugins
from lms.lms import utils as lms_utils
+
from one_lms import utils
+from one_lms.overrides.plugins import assignment_renderer, quiz_renderer
__version__ = "0.0.1"
diff --git a/one_lms/after_migrate/execute.py b/one_lms/after_migrate/execute.py
index 5f55be6..50d8c5d 100644
--- a/one_lms/after_migrate/execute.py
+++ b/one_lms/after_migrate/execute.py
@@ -1,127 +1,126 @@
-import frappe
import os
+import shlex
import shutil
import subprocess
-import shlex
+
+import frappe
+
def run_command(command_list, cwd=None):
- """
- Executes a command from a list of arguments safely.
- """
- # For display purposes, join the command list back into a readable string
- command_str = shlex.join(command_list)
- try:
- # Execute the command list, shell=False is the default and is safer
- result = subprocess.run(
- command_list,
- cwd=cwd,
- check=True,
- text=True,
- capture_output=True
- )
- # Print standard output and error
- print(result.stdout)
- if result.stderr:
- print(result.stderr)
- print(f"✅ Command '{command_str}' executed successfully.")
- except subprocess.CalledProcessError as e:
- # Handle errors in the command execution
- print(f"❌ An error occurred while running the command: {command_str}")
- print(f"Output: {e.stdout}")
- print(f"Error: {e.stderr}")
-
-
+ """
+ Executes a command from a list of arguments safely.
+ """
+ # For display purposes, join the command list back into a readable string
+ command_str = shlex.join(command_list)
+ try:
+ # Execute the command list, shell=False is the default and is safer
+ result = subprocess.run(command_list, cwd=cwd, check=True, text=True, capture_output=True)
+ # Print standard output and error
+ print(result.stdout)
+ if result.stderr:
+ print(result.stderr)
+ print(f"✅ Command '{command_str}' executed successfully.")
+ except subprocess.CalledProcessError as e:
+ # Handle errors in the command execution
+ print(f"❌ An error occurred while running the command: {command_str}")
+ print(f"Output: {e.stdout}")
+ print(f"Error: {e.stderr}")
+
+
def update_lesson():
- """
- Replaces the standard LMS Lesson.vue file with the custom version from one_lms
- and triggers a build to apply the changes.
- """
- print("🚀 Overriding LMS Lesson.vue file...")
-
- # Get the base path of the bench directory
- bench_path = frappe.utils.get_bench_path()
-
- # Define the source and destination file paths
- source_file = os.path.join(
- bench_path, "apps", "lms", "frontend", "src", "pages", "Lesson.vue"
- )
- replacement_file = os.path.join(
- bench_path, "apps", "one_lms","one_lms","public","overrides", "pages", "Lesson.vue"
- )
-
- # Ensure the custom replacement file actually exists before proceeding
- if not os.path.exists(replacement_file):
- print(f"❌ Error: Replacement file not found at: {replacement_file}")
- return False
-
- try:
- # Copy the content from your custom file to the source file, overwriting it
- print(f"📄 Copying from {replacement_file} to {source_file}")
- shutil.copy2(replacement_file, source_file)
- print("✅ Successfully replaced Lesson.vue.")
-
- return True
-
- except FileNotFoundError:
- print(f"⚠️ Warning: Original file not found at: {source_file}. Skipping replacement.")
- except Exception as e:
- print(f"🔥 An unexpected error occurred: {e}")
- frappe.log_error("LMS Lesson.vue Override Failed", frappe.get_traceback())
+ """
+ Replaces the standard LMS Lesson.vue file with the custom version from one_lms
+ and triggers a build to apply the changes.
+ """
+ print("🚀 Overriding LMS Lesson.vue file...")
+
+ # Get the base path of the bench directory
+ bench_path = frappe.utils.get_bench_path()
+
+ # Define the source and destination file paths
+ source_file = os.path.join(bench_path, "apps", "lms", "frontend", "src", "pages", "Lesson.vue")
+ replacement_file = os.path.join(
+ bench_path, "apps", "one_lms", "one_lms", "public", "overrides", "pages", "Lesson.vue"
+ )
+
+ # Ensure the custom replacement file actually exists before proceeding
+ if not os.path.exists(replacement_file):
+ print(f"❌ Error: Replacement file not found at: {replacement_file}")
+ return False
+
+ try:
+ # Copy the content from your custom file to the source file, overwriting it
+ print(f"📄 Copying from {replacement_file} to {source_file}")
+ shutil.copy2(replacement_file, source_file)
+ print("✅ Successfully replaced Lesson.vue.")
+
+ return True
+
+ except FileNotFoundError:
+ print(f"⚠️ Warning: Original file not found at: {source_file}. Skipping replacement.")
+ except Exception as e:
+ print(f"🔥 An unexpected error occurred: {e}")
+ frappe.log_error("LMS Lesson.vue Override Failed", frappe.get_traceback())
def update_course_card_overlay():
- """
- Replaces the standard LMS CourseCardOverlay.vue file with the custom version from one_lms
- and triggers a build to apply the changes.
- """
- print("🚀 Overriding LMS CourseCardOverlay.vue file...")
-
- # Get the base path of the bench directory
- bench_path = frappe.utils.get_bench_path()
-
- # Define the source and destination file paths
- source_file = os.path.join(
- bench_path, "apps", "lms", "frontend", "src", "components", "CourseCardOverlay.vue",
- )
- replacement_file = os.path.join(
- bench_path, "apps", "one_lms","one_lms","public","overrides", "components", "CourseCardOverlay.vue"
- )
-
- # Ensure the custom replacement file actually exists before proceeding
- if not os.path.exists(replacement_file):
- print(f"❌ Error: Replacement file not found at: {replacement_file}")
- return False
-
- try:
- # Copy the content from your custom file to the source file, overwriting it
- print(f"📄 Copying from {replacement_file} to {source_file}")
- shutil.copy2(replacement_file, source_file)
- print("✅ Successfully replaced CourseCardOverlay.vue.")
-
- return True
-
- except FileNotFoundError:
- print(f"⚠️ Warning: Original file not found at: {source_file}. Skipping replacement.")
- except Exception as e:
- print(f"🔥 An unexpected error occurred: {e}")
- frappe.log_error("LMS CourseCardOverlay.vue Override Failed", frappe.get_traceback())
+ """
+ Replaces the standard LMS CourseCardOverlay.vue file with the custom version from one_lms
+ and triggers a build to apply the changes.
+ """
+ print("🚀 Overriding LMS CourseCardOverlay.vue file...")
+
+ # Get the base path of the bench directory
+ bench_path = frappe.utils.get_bench_path()
+
+ # Define the source and destination file paths
+ source_file = os.path.join(
+ bench_path,
+ "apps",
+ "lms",
+ "frontend",
+ "src",
+ "components",
+ "CourseCardOverlay.vue",
+ )
+ replacement_file = os.path.join(
+ bench_path, "apps", "one_lms", "one_lms", "public", "overrides", "components", "CourseCardOverlay.vue"
+ )
+
+ # Ensure the custom replacement file actually exists before proceeding
+ if not os.path.exists(replacement_file):
+ print(f"❌ Error: Replacement file not found at: {replacement_file}")
+ return False
+
+ try:
+ # Copy the content from your custom file to the source file, overwriting it
+ print(f"📄 Copying from {replacement_file} to {source_file}")
+ shutil.copy2(replacement_file, source_file)
+ print("✅ Successfully replaced CourseCardOverlay.vue.")
+
+ return True
+
+ except FileNotFoundError:
+ print(f"⚠️ Warning: Original file not found at: {source_file}. Skipping replacement.")
+ except Exception as e:
+ print(f"🔥 An unexpected error occurred: {e}")
+ frappe.log_error("LMS CourseCardOverlay.vue Override Failed", frappe.get_traceback())
def after_migrate():
- bench_path = frappe.utils.get_bench_path()
- any_change = False
+ bench_path = frappe.utils.get_bench_path()
+ any_change = False
- if update_lesson():
- any_change = True
+ if update_lesson():
+ any_change = True
- if update_course_card_overlay():
- any_change = True
-
- if any_change:
+ if update_course_card_overlay():
+ any_change = True
- # Trigger the build process to make the frontend changes live
- print("🏗️ Running 'bench build' to apply frontend changes...")
+ if any_change:
+ # Trigger the build process to make the frontend changes live
+ print("🏗️ Running 'bench build' to apply frontend changes...")
- run_command(['bench', 'build', '--app', 'lms'], cwd=bench_path)
+ run_command(["bench", "build", "--app", "lms"], cwd=bench_path)
- print("🎉 Override process completed successfully!")
+ print("🎉 Override process completed successfully!")
diff --git a/one_lms/custom/custom_field/course_lesson.py b/one_lms/custom/custom_field/course_lesson.py
index 2b83436..78cf0f6 100644
--- a/one_lms/custom/custom_field/course_lesson.py
+++ b/one_lms/custom/custom_field/course_lesson.py
@@ -1,19 +1,19 @@
def get_course_lesson_custom_fields():
- return {
- "Course Lesson": [
- {
- "fieldname": "lms_assignment",
- "fieldtype": "Link",
- "label": "LMS Assignment",
- "options": "LMS Assignment",
- "insert_after": "section_break_16"
- },
- {
- "fieldname": "quiz",
- "fieldtype": "Link",
- "label": "Quiz",
- "options": "LMS Quiz",
- "insert_after": "column_break_9",
- }
- ]
- }
+ return {
+ "Course Lesson": [
+ {
+ "fieldname": "lms_assignment",
+ "fieldtype": "Link",
+ "label": "LMS Assignment",
+ "options": "LMS Assignment",
+ "insert_after": "section_break_16",
+ },
+ {
+ "fieldname": "quiz",
+ "fieldtype": "Link",
+ "label": "Quiz",
+ "options": "LMS Quiz",
+ "insert_after": "column_break_9",
+ },
+ ]
+ }
diff --git a/one_lms/custom/custom_field/lms_assignment_submission.py b/one_lms/custom/custom_field/lms_assignment_submission.py
index 324258d..bf09179 100644
--- a/one_lms/custom/custom_field/lms_assignment_submission.py
+++ b/one_lms/custom/custom_field/lms_assignment_submission.py
@@ -1,20 +1,20 @@
def get_lms_assignment_submission_custom_fields():
- return {
- "LMS Assignment Submission": [
- {
- "fieldname": "instructor_notified_submission",
- "fieldtype": "Check",
- "label": "Instructor Notified Submission",
- "read_only": 1,
- "insert_after": "column_break_ygdu",
- "default": "0"
- },
- {
- "fieldname": "custom_employee_id",
- "fieldtype": "Data",
- "fetch_from": "member.username",
- "label": "Employee ID",
- "insert_after": "member_name"
- }
- ]
- }
+ return {
+ "LMS Assignment Submission": [
+ {
+ "fieldname": "instructor_notified_submission",
+ "fieldtype": "Check",
+ "label": "Instructor Notified Submission",
+ "read_only": 1,
+ "insert_after": "column_break_ygdu",
+ "default": "0",
+ },
+ {
+ "fieldname": "custom_employee_id",
+ "fieldtype": "Data",
+ "fetch_from": "member.username",
+ "label": "Employee ID",
+ "insert_after": "member_name",
+ },
+ ]
+ }
diff --git a/one_lms/custom/custom_field/lms_category.py b/one_lms/custom/custom_field/lms_category.py
index 43af510..479764d 100644
--- a/one_lms/custom/custom_field/lms_category.py
+++ b/one_lms/custom/custom_field/lms_category.py
@@ -1,11 +1,11 @@
def get_lms_category_custom_fields():
- return {
- "LMS Category": [
- {
- "fieldname": "image",
- "fieldtype": "Attach Image",
- "label": "Image",
- "insert_after": "category",
- }
- ]
- }
+ return {
+ "LMS Category": [
+ {
+ "fieldname": "image",
+ "fieldtype": "Attach Image",
+ "label": "Image",
+ "insert_after": "category",
+ }
+ ]
+ }
diff --git a/one_lms/custom/custom_field/lms_course.py b/one_lms/custom/custom_field/lms_course.py
index d907fe5..1625791 100644
--- a/one_lms/custom/custom_field/lms_course.py
+++ b/one_lms/custom/custom_field/lms_course.py
@@ -1,26 +1,26 @@
def get_lms_course_custom_fields():
- return {
- "LMS Course": [
- {
- "fieldname": "allow_reenrollments",
- "fieldtype": "Check",
- "label": "Allow Re-Enrollments",
- "default": "0",
- "insert_after": "category",
- },
- {
- "fieldname": "template",
- "fieldtype": "Link",
- "label": "Template",
- "options": "Print Format",
- "depends_on": "enable_certification",
- },
- {
- "fieldname": "default_instructor",
- "fieldtype": "Select",
- "label": "Default Instructor",
- "description": "The selected instructor will be displayed on the course certificates.",
- "insert_after": "image",
- }
- ]
- }
+ return {
+ "LMS Course": [
+ {
+ "fieldname": "allow_reenrollments",
+ "fieldtype": "Check",
+ "label": "Allow Re-Enrollments",
+ "default": "0",
+ "insert_after": "category",
+ },
+ {
+ "fieldname": "template",
+ "fieldtype": "Link",
+ "label": "Template",
+ "options": "Print Format",
+ "depends_on": "enable_certification",
+ },
+ {
+ "fieldname": "default_instructor",
+ "fieldtype": "Select",
+ "label": "Default Instructor",
+ "description": "The selected instructor will be displayed on the course certificates.",
+ "insert_after": "image",
+ },
+ ]
+ }
diff --git a/one_lms/custom/custom_field/lms_enrollment.py b/one_lms/custom/custom_field/lms_enrollment.py
index 0a516fe..9ac54f1 100644
--- a/one_lms/custom/custom_field/lms_enrollment.py
+++ b/one_lms/custom/custom_field/lms_enrollment.py
@@ -1,30 +1,30 @@
def get_lms_enrollment_custom_fields():
- return {
- "LMS Enrollment": [
- {
- "fieldname": "instructor_notified_completion",
- "fieldtype": "Check",
- "label": "Instructor Notified Completion",
- "read_only": 1,
- "insert_after": "role",
- "default": "0",
- "depends_on": "eval:doc.progress==100"
- },
- {
- "fieldname": "date",
- "fieldtype": "Date",
- "label": "Date",
- "default": "Today",
- "in_filter": 1,
- "in_list_view": 1,
- },
- {
- "fieldname": "course_completion_date",
- "fieldtype": "Date",
- "label": "Course Completion Date",
- "depends_on": "eval:doc.progress==100",
- "read_only": 1,
- "in_filter": 1,
- }
- ]
- }
+ return {
+ "LMS Enrollment": [
+ {
+ "fieldname": "instructor_notified_completion",
+ "fieldtype": "Check",
+ "label": "Instructor Notified Completion",
+ "read_only": 1,
+ "insert_after": "role",
+ "default": "0",
+ "depends_on": "eval:doc.progress==100",
+ },
+ {
+ "fieldname": "date",
+ "fieldtype": "Date",
+ "label": "Date",
+ "default": "Today",
+ "in_filter": 1,
+ "in_list_view": 1,
+ },
+ {
+ "fieldname": "course_completion_date",
+ "fieldtype": "Date",
+ "label": "Course Completion Date",
+ "depends_on": "eval:doc.progress==100",
+ "read_only": 1,
+ "in_filter": 1,
+ },
+ ]
+ }
diff --git a/one_lms/custom/custom_field/lms_quiz_submission.py b/one_lms/custom/custom_field/lms_quiz_submission.py
index 723e130..5fae849 100644
--- a/one_lms/custom/custom_field/lms_quiz_submission.py
+++ b/one_lms/custom/custom_field/lms_quiz_submission.py
@@ -1,27 +1,27 @@
def get_lms_quiz_submission_custom_fields():
- return {
- "LMS Quiz Submission": [
- {
- "fieldname": "instructor_notified_submission",
- "fieldtype": "Check",
- "label": "Instructor Notified Submission",
- "read_only": 1,
- "insert_after": "result",
- "default": "0"
- },
- {
- "fieldname": "custom_employee_id",
- "fieldtype": "Data",
- "fetch_from": "member.username",
- "label": "Employee ID",
- "insert_after": "member_name"
- },
- {
- "fieldname": "custom_date",
- "fieldtype": "Datetime",
- "label": "Submission Date",
- "read_only": 1,
- "insert_after": "owner"
- }
- ]
- }
+ return {
+ "LMS Quiz Submission": [
+ {
+ "fieldname": "instructor_notified_submission",
+ "fieldtype": "Check",
+ "label": "Instructor Notified Submission",
+ "read_only": 1,
+ "insert_after": "result",
+ "default": "0",
+ },
+ {
+ "fieldname": "custom_employee_id",
+ "fieldtype": "Data",
+ "fetch_from": "member.username",
+ "label": "Employee ID",
+ "insert_after": "member_name",
+ },
+ {
+ "fieldname": "custom_date",
+ "fieldtype": "Datetime",
+ "label": "Submission Date",
+ "read_only": 1,
+ "insert_after": "owner",
+ },
+ ]
+ }
diff --git a/one_lms/custom/custom_field/lms_settings.py b/one_lms/custom/custom_field/lms_settings.py
index 6bbff7b..3c183e0 100644
--- a/one_lms/custom/custom_field/lms_settings.py
+++ b/one_lms/custom/custom_field/lms_settings.py
@@ -1,61 +1,61 @@
def get_lms_settings_custom_fields():
- return {
- "LMS Settings": [
- {
- "fieldname": "course_completion_notification_template",
- "fieldtype": "Link",
- "label": "Course Completion Notification Template",
- "options": "Email Template",
- "insert_after": "certification_template"
- },
- {
- "fieldname": "notification_settings_tab",
- "fieldtype": "Tab Break",
- "label": "Notification Settings",
- "insert_after": "course_completion_notification_template"
- },
- {
- "fieldname": "notify_instructor_course_completion_eod",
- "fieldtype": "Check",
- "label": "Notify Instructor on Course Completion by EOD",
- "default": "0",
- "description": "Check it true to send the course completion notification to the instructors assigned in the corse by end of the day(at 11:50 pm)\nYou can configure custom 'Course Completion Notification Template' from the Email Templates tab.",
- "insert_after": "notification_settings_tab"
- },
- {
- "fieldname": "notify_instructor_assignment_submission_eod",
- "fieldtype": "Check",
- "label": "Notify Instructor on Assignment Submission by EOD",
- "default": "0",
- "description": "Check it true to send the assignment submission notification to the instructors assigned in the corse by end of the day(at 11:50 pm)\nYou can configure custom 'Assignment Submission Template' from the Email Templates tab.\n\nCheck it false will send the assignment submission notification right after the creation",
- "insert_after": "notify_instructor_course_completion_eod"
- },
- {
- "fieldname": "column_break_6xas",
- "fieldtype": "Column Break",
- "insert_after": "notify_instructor_assignment_submission_eod"
- },
- {
- "fieldname": "notify_instructor_quiz_submission_eod",
- "fieldtype": "Check",
- "label": "Notify Instructor on Quiz Submission by EOD",
- "default": "0",
- "description": "Check it true to send the quiz submission notification to the instructors assigned in the corse by end of the day(at 11:50 pm)\nYou can configure custom 'Quiz Submission Notification Template' from the Email Templates tab.",
- "insert_after": "column_break_6xas"
- },
- {
- "fieldname": "quiz_submission_template",
- "fieldtype": "Link",
- "label": "Quiz Submission Notification Template",
- "options": "Email Template",
- "insert_after": "notify_instructor_quiz_submission_eod"
- },
- {
- "fieldname": "training_manager",
- "fieldtype": "Link",
- "label": "Training Manager",
- "options": "User",
- "insert_after": "search_placeholder"
- }
- ]
- }
+ return {
+ "LMS Settings": [
+ {
+ "fieldname": "course_completion_notification_template",
+ "fieldtype": "Link",
+ "label": "Course Completion Notification Template",
+ "options": "Email Template",
+ "insert_after": "certification_template",
+ },
+ {
+ "fieldname": "notification_settings_tab",
+ "fieldtype": "Tab Break",
+ "label": "Notification Settings",
+ "insert_after": "course_completion_notification_template",
+ },
+ {
+ "fieldname": "notify_instructor_course_completion_eod",
+ "fieldtype": "Check",
+ "label": "Notify Instructor on Course Completion by EOD",
+ "default": "0",
+ "description": "Check it true to send the course completion notification to the instructors assigned in the corse by end of the day(at 11:50 pm)\nYou can configure custom 'Course Completion Notification Template' from the Email Templates tab.",
+ "insert_after": "notification_settings_tab",
+ },
+ {
+ "fieldname": "notify_instructor_assignment_submission_eod",
+ "fieldtype": "Check",
+ "label": "Notify Instructor on Assignment Submission by EOD",
+ "default": "0",
+ "description": "Check it true to send the assignment submission notification to the instructors assigned in the corse by end of the day(at 11:50 pm)\nYou can configure custom 'Assignment Submission Template' from the Email Templates tab.\n\nCheck it false will send the assignment submission notification right after the creation",
+ "insert_after": "notify_instructor_course_completion_eod",
+ },
+ {
+ "fieldname": "column_break_6xas",
+ "fieldtype": "Column Break",
+ "insert_after": "notify_instructor_assignment_submission_eod",
+ },
+ {
+ "fieldname": "notify_instructor_quiz_submission_eod",
+ "fieldtype": "Check",
+ "label": "Notify Instructor on Quiz Submission by EOD",
+ "default": "0",
+ "description": "Check it true to send the quiz submission notification to the instructors assigned in the corse by end of the day(at 11:50 pm)\nYou can configure custom 'Quiz Submission Notification Template' from the Email Templates tab.",
+ "insert_after": "column_break_6xas",
+ },
+ {
+ "fieldname": "quiz_submission_template",
+ "fieldtype": "Link",
+ "label": "Quiz Submission Notification Template",
+ "options": "Email Template",
+ "insert_after": "notify_instructor_quiz_submission_eod",
+ },
+ {
+ "fieldname": "training_manager",
+ "fieldtype": "Link",
+ "label": "Training Manager",
+ "options": "User",
+ "insert_after": "search_placeholder",
+ },
+ ]
+ }
diff --git a/one_lms/custom/property_setter/course_lesson.py b/one_lms/custom/property_setter/course_lesson.py
index a0ef311..18d3944 100644
--- a/one_lms/custom/property_setter/course_lesson.py
+++ b/one_lms/custom/property_setter/course_lesson.py
@@ -1,45 +1,45 @@
def get_course_lesson_properties():
- return [
- {
- "doctype_or_field": "DocField",
- "doc_type": "Course Lesson",
- "field_name": "body",
- "property": "fieldtype",
- "value": "Text Editor"
- },
- {
- "doctype_or_field": "DocField",
- "doc_type": "Course Lesson",
- "field_name": "instructor_notes",
- "property": "fieldtype",
- "value": "Text Editor"
- },
- {
- "doctype_or_field": "DocField",
- "doc_type": "Course Lesson",
- "field_name": "question",
- "property": "read_only",
- "value": "1"
- },
- {
- "doctype_or_field": "DocField",
- "doc_type": "Course Lesson",
- "field_name": "question",
- "property": "fetch_from",
- "value": "lms_assignment.question"
- },
- {
- "doctype_or_field": "DocField",
- "doc_type": "Course Lesson",
- "field_name": "quiz_id",
- "property": "read_only",
- "value": "1"
- },
- {
- "doctype_or_field": "DocField",
- "doc_type": "Course Lesson",
- "field_name": "quiz_id",
- "property": "depends_on",
- "value": "eval:doc.quiz"
- }
- ]
\ No newline at end of file
+ return [
+ {
+ "doctype_or_field": "DocField",
+ "doc_type": "Course Lesson",
+ "field_name": "body",
+ "property": "fieldtype",
+ "value": "Text Editor",
+ },
+ {
+ "doctype_or_field": "DocField",
+ "doc_type": "Course Lesson",
+ "field_name": "instructor_notes",
+ "property": "fieldtype",
+ "value": "Text Editor",
+ },
+ {
+ "doctype_or_field": "DocField",
+ "doc_type": "Course Lesson",
+ "field_name": "question",
+ "property": "read_only",
+ "value": "1",
+ },
+ {
+ "doctype_or_field": "DocField",
+ "doc_type": "Course Lesson",
+ "field_name": "question",
+ "property": "fetch_from",
+ "value": "lms_assignment.question",
+ },
+ {
+ "doctype_or_field": "DocField",
+ "doc_type": "Course Lesson",
+ "field_name": "quiz_id",
+ "property": "read_only",
+ "value": "1",
+ },
+ {
+ "doctype_or_field": "DocField",
+ "doc_type": "Course Lesson",
+ "field_name": "quiz_id",
+ "property": "depends_on",
+ "value": "eval:doc.quiz",
+ },
+ ]
diff --git a/one_lms/custom/property_setter/lms_quiz.py b/one_lms/custom/property_setter/lms_quiz.py
index c09b58e..d46a34d 100644
--- a/one_lms/custom/property_setter/lms_quiz.py
+++ b/one_lms/custom/property_setter/lms_quiz.py
@@ -1,11 +1,11 @@
def get_lms_quiz_properties():
- return [
- {
- "doctype_or_field": "DocField",
- "doc_type": "LMS Quiz",
- "field_name": "lesson",
- "property": "read_only",
- "property_type": "Check",
- "value": "0"
- }
- ]
\ No newline at end of file
+ return [
+ {
+ "doctype_or_field": "DocField",
+ "doc_type": "LMS Quiz",
+ "field_name": "lesson",
+ "property": "read_only",
+ "property_type": "Check",
+ "value": "0",
+ }
+ ]
diff --git a/one_lms/custom/property_setter/lms_quiz_result.py b/one_lms/custom/property_setter/lms_quiz_result.py
index bb9e7b8..f371c0d 100644
--- a/one_lms/custom/property_setter/lms_quiz_result.py
+++ b/one_lms/custom/property_setter/lms_quiz_result.py
@@ -1,11 +1,11 @@
def get_lms_quiz_result_properties():
- return [
- {
- "doctype_or_field": "DocField",
- "doc_type": "LMS Quiz Result",
- "field_name": "answer",
- "property": "fieldtype",
- "property_type": "Data",
- "value": "Small Text"
- }
- ]
\ No newline at end of file
+ return [
+ {
+ "doctype_or_field": "DocField",
+ "doc_type": "LMS Quiz Result",
+ "field_name": "answer",
+ "property": "fieldtype",
+ "property_type": "Data",
+ "value": "Small Text",
+ }
+ ]
diff --git a/one_lms/hooks.py b/one_lms/hooks.py
index 2283044..9a95e01 100644
--- a/one_lms/hooks.py
+++ b/one_lms/hooks.py
@@ -30,7 +30,7 @@
app_include_css = "/assets/one_lms/css/frappe_tinymce.css"
app_include_js = [
"https://cdnjs.cloudflare.com/ajax/libs/tinymce/6.2.0/tinymce.min.js",
- "/assets/one_lms/js/frappe_tinymce.js"
+ "/assets/one_lms/js/frappe_tinymce.js",
]
# include js, css files in header of web template
@@ -55,8 +55,8 @@
]
}
doctype_js = {
- "LMS Course" : "public/js/doctype_js/lms_course.js",
- "Course Lesson" : "public/js/doctype_js/course_lesson.js"
+ "LMS Course": "public/js/doctype_js/lms_course.js",
+ "Course Lesson": "public/js/doctype_js/course_lesson.js",
}
# doctype_list_js = {"doctype" : "public/js/doctype_list.js"}
# doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"}
@@ -82,17 +82,15 @@
scheduler_events = {
"cron": {
"50 23 * * *": [
- 'one_lms.notification.notifications.notify_course_completion',
- 'one_lms.notification.notifications.notify_assignment_submission',
- 'one_lms.notification.notifications.notify_quiz_submission'
+ "one_lms.notification.notifications.notify_course_completion",
+ "one_lms.notification.notifications.notify_assignment_submission",
+ "one_lms.notification.notifications.notify_quiz_submission",
]
}
}
# Template Overrides
-override_template = {
- "courses/course.html": "one_lms/www/courses/custom_course.html"
-}
+override_template = {"courses/course.html": "one_lms/www/courses/custom_course.html"}
# Generators
# ----------
@@ -205,15 +203,13 @@
# ------------------------------
#
-after_migrate = [
- "one_lms.after_migrate.execute.after_migrate"
-]
+after_migrate = ["one_lms.after_migrate.execute.after_migrate"]
override_whitelisted_methods = {
"lms.lms.doctype.lms_assignment_submission.lms_assignment_submission.upload_assignment": "one_lms.overrides.lms_assignment_submission.upload_assignment",
# "lms.lms.doctype.course_lesson.course_lesson.save_progress": "one_lms.overrides.course_lesson.save_progress",
- "lms.lms.doctype.lms_certificate.lms_certificate.create_certificate": "one_lms.overrides.lms_certificate.create_certificate"
+ "lms.lms.doctype.lms_certificate.lms_certificate.create_certificate": "one_lms.overrides.lms_certificate.create_certificate",
}
@@ -223,7 +219,6 @@
"LMS Enrollment": "one_lms.overrides.lms_enrollment.LMSEnrollment",
"LMS Quiz Submission": "one_lms.overrides.lms_quiz_submission.LMSQuizSubmission",
"LMS Certificate": "one_lms.overrides.lms_certificate.LMSCertificate",
-
}
#
# each overriding function accepts a `data` argument;
@@ -289,4 +284,3 @@
# default_log_clearing_doctypes = {
# "Logging DocType Name": 30 # days to retain logs
# }
-
diff --git a/one_lms/notification/notifications.py b/one_lms/notification/notifications.py
index 90d8378..acdc171 100644
--- a/one_lms/notification/notifications.py
+++ b/one_lms/notification/notifications.py
@@ -1,18 +1,19 @@
import frappe
from frappe import _
-from frappe.utils import validate_email_address
from frappe.email.doctype.email_template.email_template import get_email_template
+from frappe.utils import validate_email_address
+
def notify_course_completion():
- '''
- Method to notify the course completion on daily basis
- '''
+ """
+ Method to notify the course completion on daily basis
+ """
# Check settings Notify Instructor on Course Completion by EOD is checked true
if not frappe.db.get_single_value("LMS Settings", "notify_instructor_course_completion_eod"):
return
# Get all completed course(LMS Enrollment) that not send the notification to instructor
- query = '''
+ query = """
select
course, member_name, member, name
from
@@ -21,14 +22,14 @@ def notify_course_completion():
instructor_notified_completion!=1 and progress=100
order by
course
- '''
+ """
enrollment_details = frappe.db.sql(query, as_dict=True)
enrollments = {}
for item in enrollment_details:
if item.course not in enrollments:
- enrollments[item.course] = {'course': item.course, 'members': [], 'names': []}
- enrollments[item.course]['members'].append(item.member)
- enrollments[item.course]['names'].append(item.name)
+ enrollments[item.course] = {"course": item.course, "members": [], "names": []}
+ enrollments[item.course]["members"].append(item.member)
+ enrollments[item.course]["names"].append(item.name)
enrollments = list(enrollments.values())
subject = _("Course Completion for Today")
@@ -42,22 +43,20 @@ def notify_course_completion():
for enrollment in enrollments:
# Get the instructor(s) of the course, they are the recipient(s) of the notification
instructors_data = frappe.db.get_all(
- 'Course Instructor',
- fields=['instructor'],
- filters={
- "parent": enrollment['course'], "parenttype": "LMS Course"
- }
+ "Course Instructor",
+ fields=["instructor"],
+ filters={"parent": enrollment["course"], "parenttype": "LMS Course"},
)
- instructors = [item['instructor'] for item in instructors_data]
+ instructors = [item["instructor"] for item in instructors_data]
for instructor in instructors:
if not validate_email_address(instructor):
instructors.remove(instructor)
if instructors and len(instructors) > 0:
args = {
- "course_name": frappe.db.get_value('LMS Course', enrollment['course'], 'title'),
- "course": enrollment['course'],
- "members": enrollment['members']
+ "course_name": frappe.db.get_value("LMS Course", enrollment["course"], "title"),
+ "course": enrollment["course"],
+ "members": enrollment["members"],
}
if custom_template:
@@ -76,30 +75,24 @@ def notify_course_completion():
)
# Update LMS Enrollment instructor_notified_completion
- query = '''
- update
- `tabLMS Enrollment`
- set
- instructor_notified_completion=1
- '''
- if len(enrollment['names']) == 1:
- query += "where name = '{0}'".format(enrollment['names'][0])
- else:
- query += "where name in {0}".format(tuple(enrollment['names']))
- frappe.db.sql(query)
- frappe.db.commit()
+ frappe.db.set_value(
+ "LMS Enrollment",
+ {"name": ["in", enrollment["names"]]},
+ "instructor_notified_completion",
+ 1,
+ )
def notify_assignment_submission():
- '''
- Method to notify the assignment submission on daily basis
- '''
+ """
+ Method to notify the assignment submission on daily basis
+ """
# Check settings Notify Instructor on Assignment Submission by EOD is checked true
if not frappe.db.get_single_value("LMS Settings", "notify_instructor_assignment_submission_eod"):
return
# Get all Assignment Submission that not send the notification to instructor
- query = '''
+ query = """
select
course, lesson, member_name, member, name, assignment_title, assignment
from
@@ -108,16 +101,26 @@ def notify_assignment_submission():
instructor_notified_submission!=1 and status='Not Graded' and course is NOT NULL
order by
course
- '''
+ """
submission_details = frappe.db.sql(query, as_dict=True)
submissions = {}
for item in submission_details:
if item.course not in submissions:
- submissions[item.course] = {'course': item.course, 'assignment_title': item.assignment_title, 'members': [], 'names': []}
- member_details = {'member': item.member_name, 'member_id': item.member, 'assignment_submission': item.name, 'assignment_title': item.assignment_title}
- submissions[item.course]['members'].append(member_details)
- submissions[item.course]['names'].append(item.name)
+ submissions[item.course] = {
+ "course": item.course,
+ "assignment_title": item.assignment_title,
+ "members": [],
+ "names": [],
+ }
+ member_details = {
+ "member": item.member_name,
+ "member_id": item.member,
+ "assignment_submission": item.name,
+ "assignment_title": item.assignment_title,
+ }
+ submissions[item.course]["members"].append(member_details)
+ submissions[item.course]["names"].append(item.name)
submissions = list(submissions.values())
subject = _("Assignment Submission for Today")
@@ -131,22 +134,17 @@ def notify_assignment_submission():
for submission in submissions:
# Get the instructor(s) of the course, they are the recipient(s) of the notification
instructors_data = frappe.db.get_all(
- 'Course Instructor',
- fields=['instructor'],
- filters={
- "parent": submission['course'], "parenttype": "LMS Course"
- }
+ "Course Instructor",
+ fields=["instructor"],
+ filters={"parent": submission["course"], "parenttype": "LMS Course"},
)
- instructors = [item['instructor'] for item in instructors_data]
+ instructors = [item["instructor"] for item in instructors_data]
for instructor in instructors:
if not validate_email_address(instructor):
instructors.remove(instructor)
if instructors and len(instructors) > 0:
- args = {
- "assignment_title": submission['assignment_title'],
- "members": submission['members']
- }
+ args = {"assignment_title": submission["assignment_title"], "members": submission["members"]}
if custom_template:
email_template = get_email_template(custom_template, args)
@@ -163,31 +161,25 @@ def notify_assignment_submission():
header=["Assignment Submission on LMS", "green"],
)
- # Update LMS Enrollment instructor_notified_completion
- query = '''
- update
- `tabLMS Assignment Submission`
- set
- instructor_notified_submission=1
- '''
- if len(submission['names']) == 1:
- query += "where name = '{0}'".format(submission['names'][0])
- else:
- query += "where name in {0}".format(tuple(submission['names']))
- frappe.db.sql(query)
- frappe.db.commit()
+ # Update LMS Assignment Submission instructor_notified_submission
+ frappe.db.set_value(
+ "LMS Assignment Submission",
+ {"name": ["in", submission["names"]]},
+ "instructor_notified_submission",
+ 1,
+ )
def notify_quiz_submission():
- '''
- Method to notify the quiz submission on daily basis
- '''
+ """
+ Method to notify the quiz submission on daily basis
+ """
# Check settings Notify Instructor on Quiz Submission by EOD is checked true
if not frappe.db.get_single_value("LMS Settings", "notify_instructor_quiz_submission_eod"):
return
# Get all Quiz Submission that not send the notification to instructor
- query = '''
+ query = """
select
quiz, course, member_name, member, name, score, IF(percentage >= passing_percentage, "PASS", "FAIL") as result
from
@@ -196,16 +188,27 @@ def notify_quiz_submission():
instructor_notified_submission!=1 and course is NOT NULL
order by
course
- '''
+ """
submission_details = frappe.db.sql(query, as_dict=True)
submissions = {}
for item in submission_details:
if item.course not in submissions:
- submissions[item.course] = {'course': item.course, 'quiz': frappe.db.get_value('LMS Quiz', item.quiz, 'title'), 'members': [], 'names': []}
- member_details = {'member': item.member_name, 'member_id': item.member, 'quiz_submission': item.name, 'score': item.score, 'result': item.result}
- submissions[item.course]['members'].append(member_details)
- submissions[item.course]['names'].append(item.name)
+ submissions[item.course] = {
+ "course": item.course,
+ "quiz": frappe.db.get_value("LMS Quiz", item.quiz, "title"),
+ "members": [],
+ "names": [],
+ }
+ member_details = {
+ "member": item.member_name,
+ "member_id": item.member,
+ "quiz_submission": item.name,
+ "score": item.score,
+ "result": item.result,
+ }
+ submissions[item.course]["members"].append(member_details)
+ submissions[item.course]["names"].append(item.name)
submissions = list(submissions.values())
subject = _("Quiz Submission for Today")
@@ -219,22 +222,17 @@ def notify_quiz_submission():
for submission in submissions:
# Get the instructor(s) of the course, they are the recipient(s) of the notification
instructors_data = frappe.db.get_all(
- 'Course Instructor',
- fields=['instructor'],
- filters={
- "parent": submission['course'], "parenttype": "LMS Course"
- }
+ "Course Instructor",
+ fields=["instructor"],
+ filters={"parent": submission["course"], "parenttype": "LMS Course"},
)
- instructors = [item['instructor'] for item in instructors_data]
+ instructors = [item["instructor"] for item in instructors_data]
for instructor in instructors:
if not validate_email_address(instructor):
instructors.remove(instructor)
if instructors and len(instructors) > 0:
- args = {
- "quiz": submission['quiz'],
- "members": submission['members']
- }
+ args = {"quiz": submission["quiz"], "members": submission["members"]}
if custom_template:
email_template = get_email_template(custom_template, args)
@@ -251,16 +249,10 @@ def notify_quiz_submission():
header=["Quiz Submission on LMS", "green"],
)
- # Update LMS Enrollment instructor_notified_completion
- query = '''
- update
- `tabLMS Quiz Submission`
- set
- instructor_notified_submission=1
- '''
- if len(submission['names']) == 1:
- query += "where name = '{0}'".format(submission['names'][0])
- else:
- query += "where name in {0}".format(tuple(submission['names']))
- frappe.db.sql(query)
- frappe.db.commit()
\ No newline at end of file
+ # Update LMS Quiz Submission instructor_notified_submission
+ frappe.db.set_value(
+ "LMS Quiz Submission",
+ {"name": ["in", submission["names"]]},
+ "instructor_notified_submission",
+ 1,
+ )
diff --git a/one_lms/one_lms/doctype/lms_course_enrolment_request/lms_course_enrolment_request.js b/one_lms/one_lms/doctype/lms_course_enrolment_request/lms_course_enrolment_request.js
index 2b49cde..3831199 100644
--- a/one_lms/one_lms/doctype/lms_course_enrolment_request/lms_course_enrolment_request.js
+++ b/one_lms/one_lms/doctype/lms_course_enrolment_request/lms_course_enrolment_request.js
@@ -1,29 +1,40 @@
// Copyright (c) 2025, ONE-F-M and contributors
// For license information, please see license.txt
frappe.ui.form.on("LMS Course Enrolment Request", {
- refresh(frm) {
- if (frm.doc.status === "Open") { // Show only if status is "Open"
- frm.events.enrolment_request_approve_reject(frm, "Approve");
- frm.events.enrolment_request_approve_reject(frm, "Reject");
- }
- },
- enrolment_request_approve_reject(frm, action) {
- frm.add_custom_button(action, function () {
- frappe.call({
- doc: frm.doc,
- method: "enrolment_request_approve_reject",
- args: { action: action },
- callback: function (response) {
- if (response.message === "success") {
- if (action === "Approve") {
- frappe.show_alert({ message: "Request Approved", indicator: "green" });
- } else {
- frappe.show_alert({ message: "Request Rejected", indicator: "red" });
- }
- frm.reload_doc();
- }
- }
- });
- }, "Actions");
- }
+ refresh(frm) {
+ if (frm.doc.status === "Open") {
+ // Show only if status is "Open"
+ frm.events.enrolment_request_approve_reject(frm, "Approve");
+ frm.events.enrolment_request_approve_reject(frm, "Reject");
+ }
+ },
+ enrolment_request_approve_reject(frm, action) {
+ frm.add_custom_button(
+ action,
+ function () {
+ frappe.call({
+ doc: frm.doc,
+ method: "enrolment_request_approve_reject",
+ args: { action: action },
+ callback: function (response) {
+ if (response.message === "success") {
+ if (action === "Approve") {
+ frappe.show_alert({
+ message: "Request Approved",
+ indicator: "green",
+ });
+ } else {
+ frappe.show_alert({
+ message: "Request Rejected",
+ indicator: "red",
+ });
+ }
+ frm.reload_doc();
+ }
+ },
+ });
+ },
+ "Actions"
+ );
+ },
});
diff --git a/one_lms/one_lms/doctype/lms_course_enrolment_request/lms_course_enrolment_request.py b/one_lms/one_lms/doctype/lms_course_enrolment_request/lms_course_enrolment_request.py
index d4004c9..1b01b81 100644
--- a/one_lms/one_lms/doctype/lms_course_enrolment_request/lms_course_enrolment_request.py
+++ b/one_lms/one_lms/doctype/lms_course_enrolment_request/lms_course_enrolment_request.py
@@ -4,48 +4,48 @@
import frappe
from frappe.model.document import Document
+
class LMSCourseEnrolmentRequest(Document):
- def after_insert(self):
- self.notify_instructors_on_request()
-
- def notify_instructors_on_request(self):
- try:
- instructors = self.get_instructors()
- if instructors:
- content = self.get_email_content_for_instructor()
- context = dict(
- header="Dear Instructor,
Good day.",
- document_name=self.name,
- document_type=self.doctype,
- document_link=frappe.utils.get_url(self.get_url()),
- description="A new course enrollment request has been submitted. Please review this request in the system and take the necessary action.",
- content=content,
- link_name="Link to the Course Enrolment Request",
- )
- subject = f"Course Enrollment Request: {self.course}"
-
- msg = frappe.render_template('one_lms/templates/emails/default_email.html', context=context)
- frappe.sendmail(
- recipients=instructors,
- subject=subject,
- message=msg
- )
- # Create Notification Log for each instructor
- self.create_notification_log(instructors, subject, msg)
- except Exception as e:
- frappe.log_error(message=f"Error sending instructor notification: {e}", title="LMS Enrolment Request Notification")
-
- def get_instructors(self):
- course_doc = frappe.get_doc("LMS Course", self.course)
- instructors = []
- if course_doc.instructors:
- for instructor in course_doc.instructors:
- if instructor.instructor:
- instructors.append(instructor.instructor)
- return instructors
-
- def get_email_content_for_instructor(self):
- return f"""The details of the enrolment request are shown below:
+ def after_insert(self):
+ self.notify_instructors_on_request()
+
+ def notify_instructors_on_request(self):
+ try:
+ instructors = self.get_instructors()
+ if instructors:
+ content = self.get_email_content_for_instructor()
+ context = dict(
+ header="Dear Instructor,
Good day.",
+ document_name=self.name,
+ document_type=self.doctype,
+ document_link=frappe.utils.get_url(self.get_url()),
+ description="A new course enrollment request has been submitted. Please review this request in the system and take the necessary action.",
+ content=content,
+ link_name="Link to the Course Enrolment Request",
+ )
+ subject = f"Course Enrollment Request: {self.course}"
+
+ msg = frappe.render_template("one_lms/templates/emails/default_email.html", context=context)
+ frappe.sendmail(recipients=instructors, subject=subject, message=msg)
+ # Create Notification Log for each instructor
+ self.create_notification_log(instructors, subject, msg)
+ except Exception as e:
+ frappe.log_error(
+ message=f"Error sending instructor notification: {e}",
+ title="LMS Enrolment Request Notification",
+ )
+
+ def get_instructors(self):
+ course_doc = frappe.get_doc("LMS Course", self.course)
+ instructors = []
+ if course_doc.instructors:
+ for instructor in course_doc.instructors:
+ if instructor.instructor:
+ instructors.append(instructor.instructor)
+ return instructors
+
+ def get_email_content_for_instructor(self):
+ return f"""The details of the enrolment request are shown below:
Employee ID: {self.username}
@@ -58,79 +58,83 @@ def get_email_content_for_instructor(self):
Request Date: {frappe.utils.format_datetime(self.creation, "medium")}
"""
- def create_notification_log(self, emails, subject, msg):
- for user in emails:
- notification_doc = frappe.get_doc({
- "doctype": "Notification Log",
- "subject": subject,
- "email_content": msg,
- "document_type": self.doctype,
- "document_name": self.name,
- "from_user": frappe.session.user,
- "type": "Alert",
- "for_user": user
- })
- notification_doc.insert(ignore_permissions=True)
-
- @frappe.whitelist()
- def enrolment_request_approve_reject(self, action):
- if action == "Approve":
- self.db_set("status", "Approved")
- elif action == "Reject":
- self.db_set("status", "Rejected")
-
- self.on_update()
- return "success"
-
- def on_update(self):
- if self.status == "Approved":
- self.create_enrollment()
- self.notify_member_on_instructor_action()
-
- def create_enrollment(self):
- if self.status != "Approved":
- return
- existing_enrollment = frappe.db.exists(
- "LMS Enrollment",
- {"course": self.course, "member": self.member, "member_type": "Student"},
- )
- if existing_enrollment:
- return
- frappe.get_doc({
- "doctype": "LMS Enrollment",
- "course": self.course,
- "role": "Member",
- "member_type": "Student",
- "member": self.member,
- }).insert(ignore_permissions=True)
-
- def notify_member_on_instructor_action(self):
- if self.status not in ["Approved", "Rejected"]:
- return
- content = self.get_email_content_for_member()
- context = dict(
- header="Dear {0},
Good day.".format(self.member),
- document_name=self.name,
- document_type=self.doctype,
- document_link=frappe.utils.get_url(f"lms/courses/{self.course}"),
- description=f"Your course enrollment request has been {self.status.lower()}.",
- content=content,
- link_name="Link to the Course Page",
- )
- subject = f"Course Enrollment Request for {self.course} has been {self.status.lower()}"
-
- msg = frappe.render_template('one_lms/templates/emails/default_email.html', context=context)
- # frappe.sendmail(
- # recipients=[self.member],
- # subject=subject,
- # message=msg
- # )
- # Create Notification Log for each instructor
- self.create_notification_log([self.member], subject, msg)
- self.send_push_notification()
-
- def get_email_content_for_member(self):
- return f"""
+ def create_notification_log(self, emails, subject, msg):
+ for user in emails:
+ notification_doc = frappe.get_doc(
+ {
+ "doctype": "Notification Log",
+ "subject": subject,
+ "email_content": msg,
+ "document_type": self.doctype,
+ "document_name": self.name,
+ "from_user": frappe.session.user,
+ "type": "Alert",
+ "for_user": user,
+ }
+ )
+ notification_doc.insert(ignore_permissions=True)
+
+ @frappe.whitelist()
+ def enrolment_request_approve_reject(self, action):
+ if action == "Approve":
+ self.db_set("status", "Approved")
+ elif action == "Reject":
+ self.db_set("status", "Rejected")
+
+ self.on_update()
+ return "success"
+
+ def on_update(self):
+ if self.status == "Approved":
+ self.create_enrollment()
+ self.notify_member_on_instructor_action()
+
+ def create_enrollment(self):
+ if self.status != "Approved":
+ return
+ existing_enrollment = frappe.db.exists(
+ "LMS Enrollment",
+ {"course": self.course, "member": self.member, "member_type": "Student"},
+ )
+ if existing_enrollment:
+ return
+ frappe.get_doc(
+ {
+ "doctype": "LMS Enrollment",
+ "course": self.course,
+ "role": "Member",
+ "member_type": "Student",
+ "member": self.member,
+ }
+ ).insert(ignore_permissions=True)
+
+ def notify_member_on_instructor_action(self):
+ if self.status not in ["Approved", "Rejected"]:
+ return
+ content = self.get_email_content_for_member()
+ context = dict(
+ header=f"Dear {self.member},
Good day.",
+ document_name=self.name,
+ document_type=self.doctype,
+ document_link=frappe.utils.get_url(f"lms/courses/{self.course}"),
+ description=f"Your course enrollment request has been {self.status.lower()}.",
+ content=content,
+ link_name="Link to the Course Page",
+ )
+ subject = f"Course Enrollment Request for {self.course} has been {self.status.lower()}"
+
+ msg = frappe.render_template("one_lms/templates/emails/default_email.html", context=context)
+ # frappe.sendmail(
+ # recipients=[self.member],
+ # subject=subject,
+ # message=msg
+ # )
+ # Create Notification Log for each instructor
+ self.create_notification_log([self.member], subject, msg)
+ self.send_push_notification()
+
+ def get_email_content_for_member(self):
+ return f"""
Course Name: {self.course}
Approver: {frappe.db.get_value("User", frappe.session.user, "full_name") or frappe.session.user}
@@ -138,32 +142,34 @@ def get_email_content_for_member(self):
Decision Date: {frappe.utils.format_datetime(self.modified, "medium")}
"""
-
- def send_push_notification(self):
- import requests, json
- headers = {
- "Content-Type": "application/json"
- }
- method = '/api/method/one_fm.api.api.push_notification_rest_api_for_lms'
- site = getattr(frappe.local.conf, 'push_notification_backend_url', None)
- if not site:
- frappe.log_error(title="Error Sending Notification", message="Push notification site not set in site_config.json")
- return
- site = site.strip("/")
- course_title = frappe.get_value("LMS Course", self.course, 'title')
- data = {
- "user_id": self.member
- }
- message = f"""
+ def send_push_notification(self):
+ import json
+
+ import requests
+
+ headers = {"Content-Type": "application/json"}
+ method = "/api/method/one_fm.api.api.push_notification_rest_api_for_lms"
+ site = getattr(frappe.local.conf, "push_notification_backend_url", None)
+ if not site:
+ frappe.log_error(
+ title="Error Sending Notification",
+ message="Push notification site not set in site_config.json",
+ )
+ return
+ site = site.strip("/")
+ course_title = frappe.get_value("LMS Course", self.course, "title")
+ data = {"user_id": self.member}
+ message = f"""
Your Enrollment Request for {course_title} has been {self.status.lower()}.
"""
- data['message'] = message
- site_url = site + method
- response = requests.post(site_url, data=json.dumps(data), headers=headers)
- if response.status_code == 200:
- return
- else:
- frappe.log_error(title="Error sending push notification", message=response.text)
+ data["message"] = message
+ site_url = site + method
+ response = requests.post(site_url, data=json.dumps(data), headers=headers)
+ if response.status_code == 200:
+ return
+ else:
+ frappe.log_error(title="Error sending push notification", message=response.text)
+
@frappe.whitelist()
def create_lms_course_enrolment_request(course, member=None):
@@ -176,10 +182,11 @@ def create_lms_course_enrolment_request(course, member=None):
).save(ignore_permissions=True)
return "OK"
+
@frappe.whitelist()
def has_pending_request(course, member):
- pending_request = frappe.db.exists(
- "LMS Course Enrolment Request",
- {"course": course, "member": member, "status": "Open"},
- )
- return bool(pending_request)
\ No newline at end of file
+ pending_request = frappe.db.exists(
+ "LMS Course Enrolment Request",
+ {"course": course, "member": member, "status": "Open"},
+ )
+ return bool(pending_request)
diff --git a/one_lms/one_lms/doctype/lms_course_enrolment_request/test_lms_course_enrolment_request.py b/one_lms/one_lms/doctype/lms_course_enrolment_request/test_lms_course_enrolment_request.py
index 62a8493..2e3e5fc 100644
--- a/one_lms/one_lms/doctype/lms_course_enrolment_request/test_lms_course_enrolment_request.py
+++ b/one_lms/one_lms/doctype/lms_course_enrolment_request/test_lms_course_enrolment_request.py
@@ -3,5 +3,6 @@
from frappe.tests.utils import FrappeTestCase
+
class TestLMSCourseEnrolmentRequest(FrappeTestCase):
- pass
+ pass
diff --git a/one_lms/one_lms/doctype/lms_enrollment_tool/lms_enrollment_tool.js b/one_lms/one_lms/doctype/lms_enrollment_tool/lms_enrollment_tool.js
index 75a651c..6e69c3a 100644
--- a/one_lms/one_lms/doctype/lms_enrollment_tool/lms_enrollment_tool.js
+++ b/one_lms/one_lms/doctype/lms_enrollment_tool/lms_enrollment_tool.js
@@ -2,47 +2,52 @@
// For license information, please see license.txt
frappe.ui.form.on("LMS Enrollment Tool", {
- refresh(frm) {
- frm.trigger("set_primary_action");
- frm.trigger("set_query_for_memeber");
- },
- set_primary_action(frm) {
- frm.disable_save();
- frm.page.set_primary_action(__("Enroll to the Course"), () => {
- if (frm.doc.members.length === 0) {
- frappe.msgprint({
- message: __("Please set member in the Table to enrol"),
- title: __("No Member added"),
- indicator: "red"
- });
- return;
- }
- frm.trigger("enrol_to_the_course");
- });
- },
- enrol_to_the_course(frm) {
- frappe.call({
- method: "one_lms.one_lms.doctype.lms_enrollment_tool.lms_enrollment_tool.enrol_to_the_course",
- args: {
- members: frm.doc.members,
- course: frm.doc.course
- },
- freeze: true,
- freeze_message: __("Enrolling the Members")
- }).then((r) => {
- if (!r.exc) {
- frappe.show_alert({ message: __("Enrolled successfully"), indicator: "green" });
- frm.refresh();
- }
- });
- },
- set_query_for_memeber(frm) {
- frm.set_query("member", "members", function () {
- return {
- filters: {
- ignore_user_type: 1,
- },
- }
- });
- }
+ refresh(frm) {
+ frm.trigger("set_primary_action");
+ frm.trigger("set_query_for_memeber");
+ },
+ set_primary_action(frm) {
+ frm.disable_save();
+ frm.page.set_primary_action(__("Enroll to the Course"), () => {
+ if (frm.doc.members.length === 0) {
+ frappe.msgprint({
+ message: __("Please set member in the Table to enrol"),
+ title: __("No Member added"),
+ indicator: "red",
+ });
+ return;
+ }
+ frm.trigger("enrol_to_the_course");
+ });
+ },
+ enrol_to_the_course(frm) {
+ frappe
+ .call({
+ method: "one_lms.one_lms.doctype.lms_enrollment_tool.lms_enrollment_tool.enrol_to_the_course",
+ args: {
+ members: frm.doc.members,
+ course: frm.doc.course,
+ },
+ freeze: true,
+ freeze_message: __("Enrolling the Members"),
+ })
+ .then((r) => {
+ if (!r.exc) {
+ frappe.show_alert({
+ message: __("Enrolled successfully"),
+ indicator: "green",
+ });
+ frm.refresh();
+ }
+ });
+ },
+ set_query_for_memeber(frm) {
+ frm.set_query("member", "members", function () {
+ return {
+ filters: {
+ ignore_user_type: 1,
+ },
+ };
+ });
+ },
});
diff --git a/one_lms/one_lms/doctype/lms_enrollment_tool/lms_enrollment_tool.py b/one_lms/one_lms/doctype/lms_enrollment_tool/lms_enrollment_tool.py
index 8b8631b..d1c5d4a 100644
--- a/one_lms/one_lms/doctype/lms_enrollment_tool/lms_enrollment_tool.py
+++ b/one_lms/one_lms/doctype/lms_enrollment_tool/lms_enrollment_tool.py
@@ -1,31 +1,23 @@
# Copyright (c) 2024, Frappe and contributors
# For license information, please see license.txt
-import frappe
-from frappe.model.document import Document
import json
+import frappe
+from frappe.model.document import Document
@frappe.whitelist()
def enrol_to_the_course(members, course):
- if isinstance(members, str):
- members = json.loads(members)
- for member in members:
- if not frappe.db.exists(
- "LMS Enrollment",
- {
- "member": member['member'],
- "course": course
- }
- ):
- lms_enrollment = frappe.get_doc(
- dict(
- doctype="LMS Enrollment",
- member=member['member'],
- course=course
- )
- )
- lms_enrollment.insert()
+ if isinstance(members, str):
+ members = json.loads(members)
+ for member in members:
+ if not frappe.db.exists("LMS Enrollment", {"member": member["member"], "course": course}):
+ lms_enrollment = frappe.get_doc(
+ dict(doctype="LMS Enrollment", member=member["member"], course=course)
+ )
+ lms_enrollment.insert()
+
+
class LMSEnrollmentTool(Document):
- pass
\ No newline at end of file
+ pass
diff --git a/one_lms/one_lms/doctype/lms_enrollment_tool/test_lms_enrollment_tool.py b/one_lms/one_lms/doctype/lms_enrollment_tool/test_lms_enrollment_tool.py
index 5efa876..9ab1ffc 100644
--- a/one_lms/one_lms/doctype/lms_enrollment_tool/test_lms_enrollment_tool.py
+++ b/one_lms/one_lms/doctype/lms_enrollment_tool/test_lms_enrollment_tool.py
@@ -3,5 +3,6 @@
from frappe.tests.utils import FrappeTestCase
+
class TestLMSEnrollmentTool(FrappeTestCase):
- pass
+ pass
diff --git a/one_lms/one_lms/doctype/lms_enrollment_tool_member/lms_enrollment_tool_member.py b/one_lms/one_lms/doctype/lms_enrollment_tool_member/lms_enrollment_tool_member.py
index 0c7b0cc..3f830ba 100644
--- a/one_lms/one_lms/doctype/lms_enrollment_tool_member/lms_enrollment_tool_member.py
+++ b/one_lms/one_lms/doctype/lms_enrollment_tool_member/lms_enrollment_tool_member.py
@@ -3,5 +3,6 @@
from frappe.model.document import Document
+
class LMSEnrollmentToolMember(Document):
- pass
+ pass
diff --git a/one_lms/overrides/course_lesson.py b/one_lms/overrides/course_lesson.py
index 4322d92..8d34833 100644
--- a/one_lms/overrides/course_lesson.py
+++ b/one_lms/overrides/course_lesson.py
@@ -1,12 +1,12 @@
import frappe
from lms.lms.utils import get_course_progress
+
from ...md import find_macros
+
@frappe.whitelist()
def save_progress(lesson, course):
- membership = frappe.db.exists(
- "LMS Enrollment", {"member": frappe.session.user, "course": course}
- )
+ membership = frappe.db.exists("LMS Enrollment", {"member": frappe.session.user, "course": course})
if not membership:
return 0
@@ -46,4 +46,4 @@ def save_progress(lesson, course):
if progress == 100:
frappe.db.set_value("LMS Enrollment", membership, "course_completion_date", frappe.utils.today())
frappe.db.commit()
- return progress
\ No newline at end of file
+ return progress
diff --git a/one_lms/overrides/lms_assignment_submission.py b/one_lms/overrides/lms_assignment_submission.py
index e802a87..6ca2730 100644
--- a/one_lms/overrides/lms_assignment_submission.py
+++ b/one_lms/overrides/lms_assignment_submission.py
@@ -2,61 +2,62 @@
from frappe import _
from frappe.utils import validate_url
+
@frappe.whitelist()
def upload_assignment(
- assignment_attachment=None,
- answer=None,
- assignment=None,
- lesson=None,
- status="Not Graded",
- comments=None,
- submission=None,
+ assignment_attachment=None,
+ answer=None,
+ assignment=None,
+ lesson=None,
+ status="Not Graded",
+ comments=None,
+ submission=None,
):
- if frappe.session.user == "Guest":
- return
-
- assignment_details = frappe.db.get_value(
- "LMS Assignment", assignment, ["type", "grade_assignment"], as_dict=1
- )
- if not assignment_details and not assignment and lesson:
- assignment = frappe.get_value("Course Lesson", lesson, "lms_assignment")
- assignment_details = frappe.db.get_value(
- "LMS Assignment", assignment, ["type", "grade_assignment"], as_dict=1
- )
-
- assignment_type = assignment_details.type
-
- if assignment_type in ["URL", "Text"] and not answer:
- frappe.throw(_("Please enter the URL for assignment submission."))
-
- if assignment_type == "File" and not assignment_attachment:
- frappe.throw(_("Please upload the assignment file."))
-
- if assignment_type == "URL" and not validate_url(answer):
- frappe.throw(_("Please enter a valid URL."))
-
- if submission:
- doc = frappe.get_doc("LMS Assignment Submission", submission)
- else:
- doc = frappe.get_doc(
- {
- "doctype": "LMS Assignment Submission",
- "assignment": assignment,
- "lesson": lesson,
- "member": frappe.session.user,
- "type": assignment_type,
- }
- )
-
- doc.update(
- {
- "assignment_attachment": assignment_attachment,
- "status": "Not Applicable"
- if assignment_type == "Text" and not assignment_details.grade_assignment
- else status,
- "comments": comments,
- "answer": answer,
- }
- )
- doc.save(ignore_permissions=True)
- return doc.name
+ if frappe.session.user == "Guest":
+ return
+
+ assignment_details = frappe.db.get_value(
+ "LMS Assignment", assignment, ["type", "grade_assignment"], as_dict=1
+ )
+ if not assignment_details and not assignment and lesson:
+ assignment = frappe.get_value("Course Lesson", lesson, "lms_assignment")
+ assignment_details = frappe.db.get_value(
+ "LMS Assignment", assignment, ["type", "grade_assignment"], as_dict=1
+ )
+
+ assignment_type = assignment_details.type
+
+ if assignment_type in ["URL", "Text"] and not answer:
+ frappe.throw(_("Please enter the URL for assignment submission."))
+
+ if assignment_type == "File" and not assignment_attachment:
+ frappe.throw(_("Please upload the assignment file."))
+
+ if assignment_type == "URL" and not validate_url(answer):
+ frappe.throw(_("Please enter a valid URL."))
+
+ if submission:
+ doc = frappe.get_doc("LMS Assignment Submission", submission)
+ else:
+ doc = frappe.get_doc(
+ {
+ "doctype": "LMS Assignment Submission",
+ "assignment": assignment,
+ "lesson": lesson,
+ "member": frappe.session.user,
+ "type": assignment_type,
+ }
+ )
+
+ doc.update(
+ {
+ "assignment_attachment": assignment_attachment,
+ "status": "Not Applicable"
+ if assignment_type == "Text" and not assignment_details.grade_assignment
+ else status,
+ "comments": comments,
+ "answer": answer,
+ }
+ )
+ doc.save(ignore_permissions=True)
+ return doc.name
diff --git a/one_lms/overrides/lms_batch.py b/one_lms/overrides/lms_batch.py
index daef7b2..0f0c9e6 100644
--- a/one_lms/overrides/lms_batch.py
+++ b/one_lms/overrides/lms_batch.py
@@ -2,42 +2,41 @@
from frappe.email.doctype.email_template.email_template import get_email_template
from lms.lms.doctype.lms_batch.lms_batch import LMSBatch as BaseLMSBatch
+
class LMSBatch(BaseLMSBatch):
- def send_mail(self, student):
- subject = frappe._("Enrollment Confirmation for the Next Training Batch")
- template = "batch_confirmation"
- custom_template = frappe.db.get_single_value(
- "LMS Settings", "batch_confirmation_template"
- )
+ def send_mail(self, student):
+ subject = frappe._("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,
- "end_date": self.end_date,
- "end_time": self.end_time,
- "medium": self.medium,
- "name": self.name,
- }
- if self.courses:
- args['courses'] = " \n ".join([i.title for i in self.courses])
- doc_dict = self.as_dict()
- doc_keys = doc_dict.keys()
- if doc_keys:
- for each in doc_keys:
- if not args.get(each):
- args[each] = doc_dict.get(each)
- if custom_template:
- email_template = get_email_template(custom_template, args)
- subject = email_template.get("subject")
- content = email_template.get("message")
+ args = {
+ "student_name": student.student_name,
+ "start_time": self.start_time,
+ "start_date": self.start_date,
+ "end_date": self.end_date,
+ "end_time": self.end_time,
+ "medium": self.medium,
+ "name": self.name,
+ }
+ if self.courses:
+ args["courses"] = " \n ".join([i.title for i in self.courses])
+ doc_dict = self.as_dict()
+ doc_keys = doc_dict.keys()
+ if doc_keys:
+ for each in doc_keys:
+ if not args.get(each):
+ args[each] = doc_dict.get(each)
+ 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,
- )
+ 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,
+ )
diff --git a/one_lms/overrides/lms_certificate.py b/one_lms/overrides/lms_certificate.py
index c1a4449..f3031e6 100644
--- a/one_lms/overrides/lms_certificate.py
+++ b/one_lms/overrides/lms_certificate.py
@@ -1,37 +1,39 @@
import frappe
from frappe import _
+from frappe.email.doctype.email_template.email_template import get_email_template
from frappe.model.document import Document
from frappe.utils import add_years, nowdate
-from lms.lms.utils import is_certified
-from frappe.email.doctype.email_template.email_template import get_email_template
from lms.lms.doctype.lms_certificate.lms_certificate import LMSCertificate as BaseLMSCertificate
+from lms.lms.utils import is_certified
+
class LMSCertificate(BaseLMSCertificate):
- def send_mail(self):
- subject = _("Congratulations on getting certified!")
- template = "certification"
- custom_template = frappe.db.get_single_value("LMS Settings", "certification_template")
- course_name, course_title = frappe.db.get_value("LMS Course", self.course, ["name", "title"])
+ def send_mail(self):
+ subject = _("Congratulations on getting certified!")
+ template = "certification"
+ custom_template = frappe.db.get_single_value("LMS Settings", "certification_template")
+ course_name, course_title = frappe.db.get_value("LMS Course", self.course, ["name", "title"])
+
+ args = {
+ "student_name": self.member_name,
+ "course": course_title,
+ "certificate_name": self.name,
+ "certificate_link": f"{frappe.utils.get_url()}/courses/{course_name}/{self.name}",
+ }
- args = {
- "student_name": self.member_name,
- "course": course_title,
- "certificate_name": self.name,
- "certificate_link":f"{frappe.utils.get_url()}/courses/{course_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"],
+ )
- 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"],
- )
@frappe.whitelist()
def create_certificate(course):
@@ -70,4 +72,4 @@ def create_certificate(course):
}
)
certificate.save(ignore_permissions=True)
- return certificate
\ No newline at end of file
+ return certificate
diff --git a/one_lms/overrides/lms_course.py b/one_lms/overrides/lms_course.py
index 40558a1..3f4969c 100644
--- a/one_lms/overrides/lms_course.py
+++ b/one_lms/overrides/lms_course.py
@@ -1,30 +1,34 @@
import frappe
from frappe.model.document import Document
+
def is_re_enrollment_allowed(course):
- return frappe.db.get_value("LMS Course", course, "allow_reenrollments")
+ return frappe.db.get_value("LMS Course", course, "allow_reenrollments")
+
def re_enroll_member(course, member):
- frappe.db.delete("LMS Course Progress", { "course": course, "member": member })
- enrollment = frappe.get_doc("LMS Enrollment", { "course": course, "member": member })
- enrollment.progress = 0
- enrollment.current_lesson = ""
- enrollment.save(ignore_permissions=True)
+ frappe.db.delete("LMS Course Progress", {"course": course, "member": member})
+ enrollment = frappe.get_doc("LMS Enrollment", {"course": course, "member": member})
+ enrollment.progress = 0
+ enrollment.current_lesson = ""
+ enrollment.save(ignore_permissions=True)
+
@frappe.whitelist()
def re_enroll_single_member(course, member):
- if not is_re_enrollment_allowed(course):
- frappe.throw("Re-Enrollments are not allowed in this course")
- if not frappe.db.exists("LMS Enrollment", { "course": course, "member": member }):
- frappe.throw(f"{member} is currently not enrolled in {course}")
- re_enroll_member(course=course, member=member)
- return "OK"
+ if not is_re_enrollment_allowed(course):
+ frappe.throw("Re-Enrollments are not allowed in this course")
+ if not frappe.db.exists("LMS Enrollment", {"course": course, "member": member}):
+ frappe.throw(f"{member} is currently not enrolled in {course}")
+ re_enroll_member(course=course, member=member)
+ return "OK"
+
@frappe.whitelist()
def re_enroll_all_members(course):
- if not is_re_enrollment_allowed(course):
- frappe.throw("Re-Enrollments are not allowed in this course")
- enrollments = frappe.get_all("LMS Enrollment", {"course": course}, ["member", "course"])
- for enrollment in enrollments:
- re_enroll_member(course=enrollment.course, member=enrollment.member)
- return "OK"
+ if not is_re_enrollment_allowed(course):
+ frappe.throw("Re-Enrollments are not allowed in this course")
+ enrollments = frappe.get_all("LMS Enrollment", {"course": course}, ["member", "course"])
+ for enrollment in enrollments:
+ re_enroll_member(course=enrollment.course, member=enrollment.member)
+ return "OK"
diff --git a/one_lms/overrides/lms_enrollment.py b/one_lms/overrides/lms_enrollment.py
index d2c6140..bca4b23 100644
--- a/one_lms/overrides/lms_enrollment.py
+++ b/one_lms/overrides/lms_enrollment.py
@@ -3,52 +3,48 @@
from frappe.model.document import Document
from lms.lms.doctype.lms_enrollment.lms_enrollment import LMSEnrollment as BaseLMSEnrollment
-class LMSEnrollment(BaseLMSEnrollment):
- def validate(self):
-
- if self.is_new():
- self.notify_user()
-
- def notify_user(self):
- """Notify the user that they have been Enrolled in a course. Add course details and include link as well"""
- template = "one_lms/templates/emails/lms_course_enrollment.html"
- try:
- recipient = [self.member]
- subject = "You have been Enrolled!"
- course_title = frappe.get_value("LMS Course", self.course, 'title')
- if not getattr(self, 'custom_date_', None):
- self.custom_date_ = frappe.utils.nowdate()
- args = {
- 'course_name': course_title,
- 'student_name': self.member_name,
- 'enrollment_date': self.custom_date_,
- 'course_url': f"{frappe.utils.get_url()}/courses/{self.course}/"
- }
- message = frappe.render_template(template, context=args)
- frappe.sendmail(
- recipients=recipient,
- subject=subject,
- message=message,
- header=["Course Enrollment on LMS", "green"],
- )
- frappe.msgprint("Employee Notified", alert=1)
- except Exception as e:
- frappe.msgprint("Error Notifying Employee", alert=1)
- frappe.log_error(title="Error Notifying Employee", message=e)
+class LMSEnrollment(BaseLMSEnrollment):
+ def validate(self):
+ if self.is_new():
+ self.notify_user()
-
- def before_insert(self):
- self.reset_course_progress()
+ def notify_user(self):
+ """Notify the user that they have been Enrolled in a course. Add course details and include link as well"""
+ template = "one_lms/templates/emails/lms_course_enrollment.html"
+ try:
+ recipient = [self.member]
+ subject = "You have been Enrolled!"
+ course_title = frappe.get_value("LMS Course", self.course, "title")
+ if not getattr(self, "custom_date_", None):
+ self.custom_date_ = frappe.utils.nowdate()
+ args = {
+ "course_name": course_title,
+ "student_name": self.member_name,
+ "enrollment_date": self.custom_date_,
+ "course_url": f"{frappe.utils.get_url()}/courses/{self.course}/",
+ }
+ message = frappe.render_template(template, context=args)
+ frappe.sendmail(
+ recipients=recipient,
+ subject=subject,
+ message=message,
+ header=["Course Enrollment on LMS", "green"],
+ )
+ frappe.msgprint("Employee Notified", alert=1)
+ except Exception as e:
+ frappe.msgprint("Error Notifying Employee", alert=1)
+ frappe.log_error(title="Error Notifying Employee", message=e)
- def reset_course_progress(self):
- if not frappe.db.get_value("LMS Course", self.course, "allow_reenrollments"):
- return
- if not frappe.db.exists("LMS Enrollment", {"course": self.course, "member": self.member}):
- return
- frappe.db.sql(
- "UPDATE `tabLMS Course Progress` SET status = 'Incomplete' WHERE course = %s AND member = %s",
- (self.course, self.member)
- )
+ def before_insert(self):
+ self.reset_course_progress()
-
+ def reset_course_progress(self):
+ if not frappe.db.get_value("LMS Course", self.course, "allow_reenrollments"):
+ return
+ if not frappe.db.exists("LMS Enrollment", {"course": self.course, "member": self.member}):
+ return
+ frappe.db.sql(
+ "UPDATE `tabLMS Course Progress` SET status = 'Incomplete' WHERE course = %s AND member = %s",
+ (self.course, self.member),
+ )
diff --git a/one_lms/overrides/lms_quiz_submission.py b/one_lms/overrides/lms_quiz_submission.py
index 7e38512..b183d99 100644
--- a/one_lms/overrides/lms_quiz_submission.py
+++ b/one_lms/overrides/lms_quiz_submission.py
@@ -1,11 +1,12 @@
import frappe
from lms.lms.doctype.lms_quiz_submission.lms_quiz_submission import LMSQuizSubmission as LMSBaseQuizSubmission
+
class LMSQuizSubmission(LMSBaseQuizSubmission):
- def validate(self):
- self.set_submission_date()
- super().validate()
+ def validate(self):
+ self.set_submission_date()
+ super().validate()
- def set_submission_date(self):
- if self.is_new():
- self.custom_date = frappe.utils.now_datetime()
+ def set_submission_date(self):
+ if self.is_new():
+ self.custom_date = frappe.utils.now_datetime()
diff --git a/one_lms/overrides/plugins.py b/one_lms/overrides/plugins.py
index b7239fe..e4207f8 100644
--- a/one_lms/overrides/plugins.py
+++ b/one_lms/overrides/plugins.py
@@ -1,32 +1,37 @@
import random
-import frappe
from urllib.parse import quote
+import frappe
+from frappe import _
+
+
def assignment_renderer(detail):
- supported_types = {
- "Document": ".doc,.docx,.xml,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document",
- "PDF": ".pdf",
- "Image": ".png, .jpg, .jpeg",
- "Video": "video/*",
- }
- question = detail.split("-")[0]
- file_type = frappe.db.get_value("Course Lesson", {'question': question}, 'file_type') or "PDF"
- accept = supported_types[file_type] if file_type else ""
- return frappe.render_template(
- "templates/assignment.html",
- {
- "question": question,
- "file_type": file_type,
- "accept": accept,
- },
- )
+ supported_types = {
+ "Document": ".doc,.docx,.xml,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+ "PDF": ".pdf",
+ "Image": ".png, .jpg, .jpeg",
+ "Video": "video/*",
+ }
+ question = detail.split("-")[0]
+ file_type = frappe.db.get_value("Course Lesson", {"question": question}, "file_type") or "PDF"
+ accept = supported_types[file_type] if file_type else ""
+ return frappe.render_template(
+ "templates/assignment.html",
+ {
+ "question": question,
+ "file_type": file_type,
+ "accept": accept,
+ },
+ )
+
def quiz_renderer(quiz_name):
if frappe.session.user == "Guest":
- return "
The content of this email is confidential and intended for the recipient - specified in the message only. It is strictly forbidden to share any part of this message - with any third party, without the written consent of the sender. If you received this message by - mistake, please reply to this message and follow with its deletion, so that we can ensure such + specified in the message only. It is strictly forbidden to share any part of this message + with any third party, without the written consent of the sender. If you received this message by + mistake, please reply to this message and follow with its deletion, so that we can ensure such a mistake does not occur in the future.
{{ _("The following trainees have successfully completed the course {0} on {1}").format(frappe.bold(course_name), date) }}
@@ -23,7 +23,7 @@ {% for member in members %} - {% set user = frappe.db.get_value("User", member, ["full_name", "username"], as_dict=True) %} + {% set user = frappe.db.get_value("User", member, ["full_name", "username"], as_dict=True) %}- + {{ _("Enrollment Date:") }} {{ frappe.utils.format_date(enrollment_date, "medium") }}
diff --git a/one_lms/templates/emails/lms_quiz_submission_group_template.html b/one_lms/templates/emails/lms_quiz_submission_group_template.html index 9c7e072..2210567 100644 --- a/one_lms/templates/emails/lms_quiz_submission_group_template.html +++ b/one_lms/templates/emails/lms_quiz_submission_group_template.html @@ -1,14 +1,14 @@ {% set date = frappe.format_date(frappe.utils.today()) %} -{% set course = frappe.db.get_value("LMS Course", course, ["title", "name", "image"], as_dict=True) %} +{% set course = frappe.db.get_value("LMS Course", course, ["title", "name", "image"], as_dict=True) %}{{ _("The following trainees have submitted their quiz.") }}
diff --git a/one_lms/utils.py b/one_lms/utils.py index 9285465..b67fb8f 100644 --- a/one_lms/utils.py +++ b/one_lms/utils.py @@ -3,64 +3,57 @@ from frappe.desk.doctype.notification_log.notification_log import make_notification_logs from lms.lms.utils import get_lesson_index, get_lesson_url + def create_notification_log(doc, topic): - course = topic.course if hasattr(topic, 'course') else None - instructors = frappe.db.get_all( - "Course Instructor", {"parent": course}, pluck="instructor" - ) - link = None - if topic.reference_doctype == "LMS Batch": - link = f"/batches/{topic.reference_docname}#discussions" - if topic.reference_doctype == "Course Lesson": - lesson_index = get_lesson_index(topic.reference_docname) - link = get_lesson_url(course, lesson_index) - notification = frappe._dict( - { - "subject": _("New reply on the topic {0}").format(topic.title), - "document_type": topic.reference_doctype, - "document_name": topic.reference_docname, - "for_user": topic.owner, - "from_user": doc.owner, - "link": link, - "type": "Alert", - } - ) - users = [] - if doc.owner != topic.owner: - users.append(topic.owner) + course = topic.course if hasattr(topic, "course") else None + instructors = frappe.db.get_all("Course Instructor", {"parent": course}, pluck="instructor") + link = None + if topic.reference_doctype == "LMS Batch": + link = f"/batches/{topic.reference_docname}#discussions" + if topic.reference_doctype == "Course Lesson": + lesson_index = get_lesson_index(topic.reference_docname) + link = get_lesson_url(course, lesson_index) + notification = frappe._dict( + { + "subject": _("New reply on the topic {0}").format(topic.title), + "document_type": topic.reference_doctype, + "document_name": topic.reference_docname, + "for_user": topic.owner, + "from_user": doc.owner, + "link": link, + "type": "Alert", + } + ) + users = [] + if doc.owner != topic.owner: + users.append(topic.owner) - if doc.owner not in instructors: - users += instructors - make_notification_logs(notification, users) - if not member: - member = frappe.session.user + if doc.owner not in instructors: + users += instructors + make_notification_logs(notification, users) - return frappe.db.get_value( - "LMS Course Progress", - {"course": course, "owner": member, "lesson": lesson}, - ["status"], - ) def get_course_lessons_progress(course): - """ - Fetch the progress of all lessons in the given course for a logged in user. - Args: - course (str): Name of the course. - Returns: - dict: A dictionary mapping lesson names to their progress status ('Complete' or other). - """ - lessons = frappe.get_all("Course Lesson", filters={"course": course}, fields=["name"]) - progress_data = {} - for lesson in lessons: - progress_data[lesson['name']] = get_progress(course, lesson['name']) - return progress_data + """ + Fetch the progress of all lessons in the given course for a logged in user. + Args: + course (str): Name of the course. + Returns: + dict: A dictionary mapping lesson names to their progress status ('Complete' or other). + """ + lessons = frappe.get_all("Course Lesson", filters={"course": course}, fields=["name"]) + progress_data = {} + for lesson in lessons: + progress_data[lesson["name"]] = get_progress(course, lesson["name"]) + return progress_data + def get_progress(course, lesson, member=None): - if not member: - member = frappe.session.user + if not member: + member = frappe.session.user - return frappe.db.get_value( - "LMS Course Progress", - {"course": course, "owner": member, "lesson": lesson}, - ["status"], - ) + return frappe.db.get_value( + "LMS Course Progress", + {"course": course, "owner": member, "lesson": lesson}, + ["status"], + ) diff --git a/one_lms/www/lms.py b/one_lms/www/lms.py index 86f7beb..d45e3f2 100644 --- a/one_lms/www/lms.py +++ b/one_lms/www/lms.py @@ -5,19 +5,19 @@ def get_context(context): - - app_path = frappe.form_dict.get("app_path", "") + app_path = frappe.form_dict.get("app_path", "") - if app_path and '/files/' in app_path: - if 'private/files/' in app_path: - new_path = '/' + app_path[app_path.index('private/files/'):] - elif '/files/' in app_path: - new_path = '/' + app_path[app_path.index('files/'):] - else: - new_path = f'/files/{app_path.split("/")[-1]}' - - frappe.local.flags.redirect_location = new_path - raise frappe.Redirect - - from lms.www.lms import get_context as lms_get_context - return lms_get_context() \ No newline at end of file + if app_path and "/files/" in app_path: + if "private/files/" in app_path: + new_path = "/" + app_path[app_path.index("private/files/") :] + elif "/files/" in app_path: + new_path = "/" + app_path[app_path.index("files/") :] + else: + new_path = f'/files/{app_path.split("/")[-1]}' + + frappe.local.flags.redirect_location = new_path + raise frappe.Redirect + + from lms.www.lms import get_context as lms_get_context + + return lms_get_context()