-
{{ row.idx }}. {{ row.question }}
+
+
+
@@ -67,7 +74,7 @@ import {
Button,
Badge,
} from 'frappe-ui'
-import { computed, onMounted, inject } from 'vue'
+import { computed, onBeforeUnmount, onMounted, inject } from 'vue'
import { useRouter } from 'vue-router'
import { showToast } from '@/utils'
@@ -77,8 +84,25 @@ const user = inject('$user')
onMounted(() => {
if (!user.data?.is_instructor && !user.data?.is_moderator)
router.push({ name: 'Courses' })
+
+ window.addEventListener('keydown', keyboardShortcut)
})
+onBeforeUnmount(() => {
+ window.removeEventListener('keydown', keyboardShortcut)
+})
+
+const keyboardShortcut = (e) => {
+ if (
+ e.key === 's' &&
+ (e.ctrlKey || e.metaKey) &&
+ !e.target.classList.contains('ProseMirror')
+ ) {
+ saveSubmission()
+ e.preventDefault()
+ }
+}
+
const props = defineProps({
submission: {
type: String,
diff --git a/frontend/src/utils/quiz.js b/frontend/src/utils/quiz.js
index e9bd1e9f..3758eb99 100644
--- a/frontend/src/utils/quiz.js
+++ b/frontend/src/utils/quiz.js
@@ -60,6 +60,9 @@ export class Quiz {
}
renderQuizModal() {
+ if (this.readOnly) {
+ return
+ }
const app = createApp(QuizPlugin, {
onQuizAddition: (quiz) => {
this.data.quiz = quiz
diff --git a/lms/lms/doctype/lms_program/lms_program.json b/lms/lms/doctype/lms_program/lms_program.json
index 5b4843fb..f92c689a 100644
--- a/lms/lms/doctype/lms_program/lms_program.json
+++ b/lms/lms/doctype/lms_program/lms_program.json
@@ -34,7 +34,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2024-11-20 12:26:02.214628",
+ "modified": "2024-11-28 22:06:16.742867",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Program",
@@ -80,5 +80,6 @@
],
"sort_field": "creation",
"sort_order": "DESC",
- "states": []
+ "states": [],
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/lms/lms/doctype/lms_quiz/lms_quiz.py b/lms/lms/doctype/lms_quiz/lms_quiz.py
index 30630ef1..c6e3a089 100644
--- a/lms/lms/doctype/lms_quiz/lms_quiz.py
+++ b/lms/lms/doctype/lms_quiz/lms_quiz.py
@@ -134,7 +134,6 @@ def quiz_summary(quiz, results):
result["marks"] = marks
score += marks
- del result["question_name"]
else:
result["is_correct"] = 0
is_open_ended = True
diff --git a/lms/lms/doctype/lms_quiz_submission/lms_quiz_submission.py b/lms/lms/doctype/lms_quiz_submission/lms_quiz_submission.py
index 11c4ef0b..0998c3f6 100644
--- a/lms/lms/doctype/lms_quiz_submission/lms_quiz_submission.py
+++ b/lms/lms/doctype/lms_quiz_submission/lms_quiz_submission.py
@@ -5,6 +5,7 @@ import frappe
from frappe.model.document import Document
from frappe.utils import cint
from frappe import _
+from frappe.desk.doctype.notification_log.notification_log import make_notification_logs
class LMSQuizSubmission(Document):
@@ -12,6 +13,9 @@ class LMSQuizSubmission(Document):
self.validate_marks()
self.set_percentage()
+ def on_update(self):
+ self.notify_member()
+
def validate_marks(self):
for row in self.result:
if cint(row.marks) > cint(row.marks_out_of):
@@ -26,3 +30,24 @@ class LMSQuizSubmission(Document):
def set_percentage(self):
if self.score and self.score_out_of:
self.percentage = (self.score / self.score_out_of) * 100
+
+ def notify_member(self):
+ if self.score != 0 and self.has_value_changed("score"):
+ notification = frappe._dict(
+ {
+ "subject": _("You have got a score of {0} for the quiz {1}").format(
+ self.score, self.quiz_title
+ ),
+ "email_content": _(
+ "There has been an update on your submission. You have got a score of {0} for the quiz {1}"
+ ).format(self.score, self.quiz_title),
+ "document_type": self.doctype,
+ "document_name": self.name,
+ "for_user": self.member,
+ "from_user": "Administrator",
+ "type": "Alert",
+ "link": "",
+ }
+ )
+
+ make_notification_logs(notification, [self.member])
diff --git a/lms/lms/models.py b/lms/lms/models.py
deleted file mode 100644
index f75e934e..00000000
--- a/lms/lms/models.py
+++ /dev/null
@@ -1,6 +0,0 @@
-"""Handy module to make access to all doctypes from a single place.
-"""
-from .doctype.lms_enrollment.lms_enrollment import (
- LMSBatchMembership as Membership,
-)
-from .doctype.lms_course.lms_course import LMSCourse as Course
diff --git a/lms/lms/payments.py b/lms/lms/payments.py
index 7cff8ca7..3d003233 100644
--- a/lms/lms/payments.py
+++ b/lms/lms/payments.py
@@ -1,13 +1,16 @@
import frappe
-from payments.utils import get_payment_gateway_controller
def get_payment_gateway():
+
return frappe.db.get_single_value("LMS Settings", "payment_gateway")
def get_controller(payment_gateway):
- return get_payment_gateway_controller(payment_gateway)
+ if "payments" in frappe.get_installed_apps():
+ from payments.utils import get_payment_gateway_controller
+
+ return get_payment_gateway_controller(payment_gateway)
def validate_currency(payment_gateway, currency):
diff --git a/lms/lms/utils.py b/lms/lms/utils.py
index 0b973308..933d626d 100644
--- a/lms/lms/utils.py
+++ b/lms/lms/utils.py
@@ -6,11 +6,7 @@ import razorpay
import requests
from frappe import _
from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_result
-from frappe.desk.doctype.notification_log.notification_log import (
- make_notification_logs,
- enqueue_create_notification,
- get_title,
-)
+from frappe.desk.doctype.notification_log.notification_log import make_notification_logs
from frappe.desk.search import get_user_groups
from frappe.desk.notifications import extract_mentions
from frappe.utils import (
@@ -858,7 +854,8 @@ def get_telemetry_boot_info():
@frappe.whitelist()
def is_onboarding_complete():
if not has_course_moderator_role():
- return {"is_onboarded": False}
+ return {"is_onboarded": True}
+
course_created = frappe.db.a_row_exists("LMS Course")
chapter_created = frappe.db.a_row_exists("Course Chapter")
lesson_created = frappe.db.a_row_exists("Course Lesson")
@@ -1774,8 +1771,18 @@ def get_programs():
"LMS Program Course", {"parent": program.name}, ["course"], order_by="idx"
)
program.courses = []
- for course in program_courses:
- program.courses.append(get_course_details(course.course))
+ previous_progress = 0
+ for i, course in enumerate(program_courses):
+ details = get_course_details(course.course)
+ if i == 0:
+ details.eligible = True
+ elif previous_progress == 100:
+ details.eligible = True
+ else:
+ details.eligible = False
+
+ previous_progress = details.membership.progress if details.membership else 0
+ program.courses.append(details)
program.members = frappe.db.count("LMS Program Member", {"parent": program.name})
diff --git a/lms/patches.txt b/lms/patches.txt
index 4f11ed08..c365311d 100644
--- a/lms/patches.txt
+++ b/lms/patches.txt
@@ -94,4 +94,5 @@ lms.patches.v2_0.delete_certificate_request_notification #18-09-2024
lms.patches.v2_0.add_course_statistics #21-10-2024
lms.patches.v2_0.give_discussions_permissions
lms.patches.v2_0.delete_web_forms
-lms.patches.v2_0.update_desk_access_for_lms_roles
\ No newline at end of file
+lms.patches.v2_0.update_desk_access_for_lms_roles
+lms.patches.v2_0.update_quiz_submission_data
\ No newline at end of file
diff --git a/lms/patches/v2_0/update_quiz_submission_data.py b/lms/patches/v2_0/update_quiz_submission_data.py
new file mode 100644
index 00000000..88d78ea9
--- /dev/null
+++ b/lms/patches/v2_0/update_quiz_submission_data.py
@@ -0,0 +1,47 @@
+import frappe
+
+
+def execute():
+ set_question_data()
+ set_submission_data()
+
+
+def set_question_data():
+ questions = frappe.get_all("LMS Quiz Question", fields=["name", "question"])
+
+ for question in questions:
+ question_doc = frappe.db.get_value(
+ "LMS Question", question.question, ["question", "type"], as_dict=1
+ )
+ frappe.db.set_value(
+ "LMS Quiz Question",
+ question.name,
+ {"question_detail": question_doc.question, "type": question_doc.type},
+ )
+
+
+def set_submission_data():
+ submissions = frappe.get_all("LMS Quiz Submission", fields=["name", "quiz"])
+
+ for submission in submissions:
+ quiz_title = frappe.db.get_value("LMS Quiz", submission.quiz, "title")
+ frappe.db.set_value("LMS Quiz Submission", submission.name, "quiz_title", quiz_title)
+
+ questions = frappe.get_all(
+ "LMS Quiz Result", filters={"parent": submission.name}, fields=["question_name"]
+ )
+
+ for question in questions:
+ if question.question_name:
+ marks_out_of = frappe.db.get_value(
+ "LMS Quiz Question",
+ {"parent": submission.quiz, "question": question.question_name},
+ ["marks"],
+ )
+
+ frappe.db.set_value(
+ "LMS Quiz Result",
+ {"parent": submission.name, "question_name": question.question_name},
+ "marks_out_of",
+ marks_out_of,
+ )
diff --git a/lms/www/utils.py b/lms/www/utils.py
deleted file mode 100644
index fa9fd1a1..00000000
--- a/lms/www/utils.py
+++ /dev/null
@@ -1,74 +0,0 @@
-import frappe
-
-from lms.lms.utils import get_lesson_url, get_lessons, get_membership
-from frappe.utils import cstr
-from lms.lms.utils import redirect_to_courses_list
-
-
-def get_common_context(context):
- context.no_cache = 1
-
- try:
- batch_name = frappe.form_dict["batch"]
- except KeyError:
- batch_name = None
-
- course = frappe.db.get_value(
- "LMS Course",
- frappe.form_dict["course"],
- ["name", "title", "video_link", "enable_certification", "status"],
- as_dict=True,
- )
- if not course:
- redirect_to_courses_list()
-
- context.course = course
- context.lessons = get_lessons(course.name)
- membership = get_membership(course.name, frappe.session.user, batch_name)
- context.membership = membership
- context.progress = frappe.utils.cint(membership.progress) if membership else 0
- context.batch_old = (
- membership.batch_old if membership and membership.batch_old else None
- )
- context.course.query_parameter = (
- "?batch=" + membership.batch_old if membership and membership.batch_old else ""
- )
- context.livecode_url = get_livecode_url()
-
-
-def get_livecode_url():
- return frappe.db.get_single_value("LMS Settings", "livecode_url")
-
-
-def redirect_to_lesson(course, index_="1.1"):
- frappe.local.flags.redirect_location = (
- get_lesson_url(course.name, index_) + course.query_parameter
- )
- raise frappe.Redirect
-
-
-def get_current_lesson_details(lesson_number, context, is_edit=False):
- details_list = list(filter(lambda x: cstr(x.number) == lesson_number, context.lessons))
-
- if not len(details_list):
- if is_edit:
- return None
- else:
- redirect_to_lesson(context.course)
-
- lesson_info = details_list[0]
- lesson_info.body = lesson_info.body.replace('"', "'")
- return lesson_info
-
-
-def is_student(batch, member=None):
- if not member:
- member = frappe.session.user
-
- return frappe.db.exists(
- "Batch Student",
- {
- "student": member,
- "parent": batch,
- },
- )