+
+ {{ __('Find the perfect job for you') }}
+
+
+ Assignment: ${assignment}
+
+
`
+ return
+ }
+
+ renderAssignmentModal() {
+ if (this.readOnly) {
+ return
+ }
+ const app = createApp(AssessmentPlugin, {
+ onAddition: (assignment) => {
+ this.data.assignment = assignment
+ this.renderAssignment(assignment)
+ },
+ })
+ app.use(translationPlugin)
+ app.mount(this.wrapper)
+ }
+
+ save(blockContent) {
+ return {
+ assignment: this.data.assignment,
+ }
+ }
+}
diff --git a/frontend/src/utils/index.js b/frontend/src/utils/index.js
index 07c04d483..7e99aee1a 100644
--- a/frontend/src/utils/index.js
+++ b/frontend/src/utils/index.js
@@ -1,6 +1,7 @@
import { toast } from 'frappe-ui'
import { useTimeAgo } from '@vueuse/core'
import { Quiz } from '@/utils/quiz'
+import { Assignment } from '@/utils/assignment'
import { Upload } from '@/utils/upload'
import { Markdown } from '@/utils/markdownParser'
import Header from '@editorjs/header'
@@ -155,6 +156,7 @@ export function getEditorTools() {
},
},
quiz: Quiz,
+ assignment: Assignment,
upload: Upload,
markdown: Markdown,
image: SimpleImage,
diff --git a/frontend/src/utils/quiz.js b/frontend/src/utils/quiz.js
index 3758eb99b..d636721a0 100644
--- a/frontend/src/utils/quiz.js
+++ b/frontend/src/utils/quiz.js
@@ -1,5 +1,5 @@
import QuizBlock from '@/components/QuizBlock.vue'
-import QuizPlugin from '@/components/QuizPlugin.vue'
+import AssessmentPlugin from '@/components/AssessmentPlugin.vue'
import { createApp, h } from 'vue'
import { usersStore } from '../stores/user'
import translationPlugin from '../translation'
@@ -63,8 +63,8 @@ export class Quiz {
if (this.readOnly) {
return
}
- const app = createApp(QuizPlugin, {
- onQuizAddition: (quiz) => {
+ const app = createApp(AssessmentPlugin, {
+ onAddition: (quiz) => {
this.data.quiz = quiz
this.renderQuiz(quiz)
},
diff --git a/lms/lms/api.py b/lms/lms/api.py
index e6430277f..347e3ac1f 100644
--- a/lms/lms/api.py
+++ b/lms/lms/api.py
@@ -17,6 +17,7 @@
from typing import Optional
from lms.lms.utils import get_average_rating, get_lesson_count
from xml.dom.minidom import parseString
+from lms.lms.doctype.course_lesson.course_lesson import save_progress
@frappe.whitelist()
@@ -168,6 +169,7 @@ def get_user_info():
user.is_instructor = "Course Creator" in user.roles
user.is_moderator = "Moderator" in user.roles
user.is_evaluator = "Batch Evaluator" in user.roles
+ user.is_student = "LMS Student" in user.roles
return user
@@ -1030,3 +1032,14 @@ def delete_scorm_package(scorm_package_path):
scorm_package_path = frappe.get_site_path("public", scorm_package_path[1:])
if os.path.exists(scorm_package_path):
shutil.rmtree(scorm_package_path)
+
+
+@frappe.whitelist()
+def mark_lesson_progress(course, chapter_number, lesson_number):
+ chapter_name = frappe.get_value(
+ "Chapter Reference", {"parent": course, "idx": chapter_number}, "chapter"
+ )
+ lesson_name = frappe.get_value(
+ "Lesson Reference", {"parent": chapter_name, "idx": lesson_number}, "lesson"
+ )
+ save_progress(lesson_name, course)
diff --git a/lms/lms/doctype/course_lesson/course_lesson.py b/lms/lms/doctype/course_lesson/course_lesson.py
index 2955305b6..40d47e4f6 100644
--- a/lms/lms/doctype/course_lesson/course_lesson.py
+++ b/lms/lms/doctype/course_lesson/course_lesson.py
@@ -89,27 +89,25 @@ def save_progress(lesson, course):
"LMS Enrollment", {"course": course, "member": frappe.session.user}
)
if not membership:
- return
+ return 0
frappe.db.set_value("LMS Enrollment", membership, "current_lesson", lesson)
-
- if frappe.db.exists(
+ already_completed = frappe.db.exists(
"LMS Course Progress", {"lesson": lesson, "member": frappe.session.user}
- ):
- return
+ )
quiz_completed = get_quiz_progress(lesson)
- if not quiz_completed:
- return 0
+ assignment_completed = get_assignment_progress(lesson)
- frappe.get_doc(
- {
- "doctype": "LMS Course Progress",
- "lesson": lesson,
- "status": "Complete",
- "member": frappe.session.user,
- }
- ).save(ignore_permissions=True)
+ if not already_completed and quiz_completed and assignment_completed:
+ frappe.get_doc(
+ {
+ "doctype": "LMS Course Progress",
+ "lesson": lesson,
+ "status": "Complete",
+ "member": frappe.session.user,
+ }
+ ).save(ignore_permissions=True)
progress = get_course_progress(course)
capture_progress_for_analytics(progress, course)
@@ -159,6 +157,32 @@ def get_quiz_progress(lesson):
return True
+def get_assignment_progress(lesson):
+ lesson_details = frappe.db.get_value(
+ "Course Lesson", lesson, ["body", "content"], as_dict=1
+ )
+ assignments = []
+
+ if lesson_details.content:
+ content = json.loads(lesson_details.content)
+
+ for block in content.get("blocks"):
+ if block.get("type") == "assignment":
+ assignments.append(block.get("data").get("assignment"))
+
+ elif lesson_details.body:
+ macros = find_macros(lesson_details.body)
+ assignments = [value for name, value in macros if name == "Assignment"]
+
+ for assignment in assignments:
+ if not frappe.db.exists(
+ "LMS Assignment Submission",
+ {"assignment": assignment, "member": frappe.session.user},
+ ):
+ return False
+ return True
+
+
@frappe.whitelist()
def get_lesson_info(chapter):
return frappe.db.get_value("Course Chapter", chapter, "course")
diff --git a/lms/lms/doctype/lms_assignment/lms_assignment.json b/lms/lms/doctype/lms_assignment/lms_assignment.json
index a0e3dde8e..dcd21f23f 100644
--- a/lms/lms/doctype/lms_assignment/lms_assignment.json
+++ b/lms/lms/doctype/lms_assignment/lms_assignment.json
@@ -9,10 +9,11 @@
"engine": "InnoDB",
"field_order": [
"title",
- "grade_assignment",
"question",
"column_break_hmwv",
"type",
+ "grade_assignment",
+ "section_break_sjti",
"show_answer",
"answer"
],
@@ -20,7 +21,8 @@
{
"fieldname": "question",
"fieldtype": "Text Editor",
- "label": "Question"
+ "label": "Question",
+ "reqd": 1
},
{
"fieldname": "type",
@@ -28,14 +30,16 @@
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Type",
- "options": "Document\nPDF\nURL\nImage\nText"
+ "options": "Document\nPDF\nURL\nImage\nText",
+ "reqd": 1
},
{
"fieldname": "title",
"fieldtype": "Data",
"in_list_view": 1,
"in_standard_filter": 1,
- "label": "Title"
+ "label": "Title",
+ "reqd": 1
},
{
"fieldname": "column_break_hmwv",
@@ -60,11 +64,15 @@
"fieldname": "grade_assignment",
"fieldtype": "Check",
"label": "Grade Assignment"
+ },
+ {
+ "fieldname": "section_break_sjti",
+ "fieldtype": "Section Break"
}
],
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2024-04-05 12:01:36.601160",
+ "modified": "2024-12-24 09:36:31.464508",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Assignment",
diff --git a/lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.json b/lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.json
index c9745ae6b..00ac7f09b 100644
--- a/lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.json
+++ b/lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.json
@@ -14,19 +14,17 @@
"member",
"member_name",
"section_break_dlzh",
- "question",
- "column_break_zvis",
"assignment_attachment",
"answer",
- "section_break_rqal",
- "status",
+ "column_break_oqqy",
"evaluator",
- "column_break_esgd",
+ "status",
"comments",
- "section_break_cwaw",
- "lesson",
+ "section_break_rqal",
+ "question",
+ "column_break_esgd",
"course",
- "column_break_ygdu"
+ "lesson"
],
"fields": [
{
@@ -89,8 +87,7 @@
"fieldname": "evaluator",
"fieldtype": "Link",
"label": "Evaluator",
- "options": "User",
- "read_only": 1
+ "options": "User"
},
{
"depends_on": "eval:!([\"URL\", \"Text\"]).includes(doc.type);",
@@ -128,14 +125,6 @@
"fieldname": "column_break_esgd",
"fieldtype": "Column Break"
},
- {
- "fieldname": "section_break_cwaw",
- "fieldtype": "Section Break"
- },
- {
- "fieldname": "column_break_ygdu",
- "fieldtype": "Column Break"
- },
{
"depends_on": "eval:([\"URL\", \"Text\"]).includes(doc.type);",
"fieldname": "answer",
@@ -148,14 +137,14 @@
"fieldtype": "Section Break"
},
{
- "fieldname": "column_break_zvis",
+ "fieldname": "column_break_oqqy",
"fieldtype": "Column Break"
}
],
"index_web_pages_for_search": 1,
"links": [],
"make_attachments_public": 1,
- "modified": "2024-04-05 15:57:22.758563",
+ "modified": "2024-12-24 21:22:35.212732",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Assignment Submission",
diff --git a/lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.py b/lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.py
index 492fdf4a7..a1c30cbe7 100644
--- a/lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.py
+++ b/lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.py
@@ -6,12 +6,14 @@
from frappe.model.document import Document
from frappe.utils import validate_url, validate_email_address
from frappe.email.doctype.email_template.email_template import get_email_template
+from frappe.desk.doctype.notification_log.notification_log import make_notification_logs
class LMSAssignmentSubmission(Document):
def validate(self):
self.validate_duplicates()
self.validate_url()
+ self.validate_status()
def after_insert(self):
if not frappe.flags.in_test:
@@ -69,6 +71,28 @@ def send_mail(self):
header=[subject, "green"],
)
+ def validate_status(self):
+ doc_before_save = self.get_doc_before_save()
+ if doc_before_save.status != self.status or doc_before_save.comments != self.comments:
+ self.trigger_update_notification()
+
+ def trigger_update_notification(self):
+ notification = frappe._dict(
+ {
+ "subject": _(
+ "There has been an update on your submission for assignment {0}"
+ ).format(self.assignment_title),
+ "email_content": self.comments,
+ "document_type": self.doctype,
+ "document_name": self.name,
+ "for_user": self.owner,
+ "from_user": self.evaluator,
+ "type": "Alert",
+ "link": f"/assignment-submission/{self.assignment}/{self.name}",
+ }
+ )
+ make_notification_logs(notification, [self.member])
+
@frappe.whitelist()
def upload_assignment(
diff --git a/lms/lms/utils.py b/lms/lms/utils.py
index 68234f534..08ef84ce4 100644
--- a/lms/lms/utils.py
+++ b/lms/lms/utils.py
@@ -1421,7 +1421,7 @@ def get_quiz_details(assessment, member):
if len(existing_submission):
assessment.submission = existing_submission[0]
assessment.completed = True
- assessment.status = assessment.submission.score
+ assessment.status = assessment.submission.percentage or assessment.submission.score
else:
assessment.status = "Not Attempted"
assessment.color = "red"