feat: Timer in LMS Quiz (#24246)
* feat: new fields in quiz doctypes
* feat: timer in lms quiz
* fix: variable initialisation
* fix: context, exception fix
* fix:sider
* fix:sider
* fix: indentation
* fix: timer
* fix: sider
* fix: return value and format
* fix: show time taken only after all attempts are over
* fix: sider
Co-authored-by: pateljannat <jannatpatel@MacBook-Air.local>
Co-authored-by: Marica <maricadsouza221197@gmail.com>
diff --git a/erpnext/education/doctype/course_enrollment/course_enrollment.py b/erpnext/education/doctype/course_enrollment/course_enrollment.py
index f7aa6e9..2b3acf1 100644
--- a/erpnext/education/doctype/course_enrollment/course_enrollment.py
+++ b/erpnext/education/doctype/course_enrollment/course_enrollment.py
@@ -41,7 +41,7 @@
frappe.throw(_("Student is already enrolled via Course Enrollment {0}").format(
get_link_to_form("Course Enrollment", enrollment)), title=_('Duplicate Entry'))
- def add_quiz_activity(self, quiz_name, quiz_response, answers, score, status):
+ def add_quiz_activity(self, quiz_name, quiz_response, answers, score, status, time_taken):
result = {k: ('Correct' if v else 'Wrong') for k,v in answers.items()}
result_data = []
for key in answers:
@@ -66,7 +66,8 @@
"activity_date": frappe.utils.datetime.datetime.now(),
"result": result_data,
"score": score,
- "status": status
+ "status": status,
+ "time_taken": time_taken
}).insert(ignore_permissions = True)
def add_activity(self, content_type, content):
diff --git a/erpnext/education/doctype/quiz/quiz.json b/erpnext/education/doctype/quiz/quiz.json
index 569c281..16d7d7e 100644
--- a/erpnext/education/doctype/quiz/quiz.json
+++ b/erpnext/education/doctype/quiz/quiz.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:title",
@@ -12,7 +13,10 @@
"quiz_configuration_section",
"passing_score",
"max_attempts",
- "grading_basis"
+ "grading_basis",
+ "column_break_7",
+ "is_time_bound",
+ "duration"
],
"fields": [
{
@@ -58,9 +62,26 @@
"fieldtype": "Select",
"label": "Grading Basis",
"options": "Latest Highest Score\nLatest Attempt"
+ },
+ {
+ "default": "0",
+ "fieldname": "is_time_bound",
+ "fieldtype": "Check",
+ "label": "Is Time-Bound"
+ },
+ {
+ "depends_on": "is_time_bound",
+ "fieldname": "duration",
+ "fieldtype": "Duration",
+ "label": "Duration"
+ },
+ {
+ "fieldname": "column_break_7",
+ "fieldtype": "Column Break"
}
],
- "modified": "2019-06-12 12:23:57.020508",
+ "links": [],
+ "modified": "2020-12-24 15:41:35.043262",
"modified_by": "Administrator",
"module": "Education",
"name": "Quiz",
diff --git a/erpnext/education/doctype/quiz_activity/quiz_activity.json b/erpnext/education/doctype/quiz_activity/quiz_activity.json
index e78db42..742c887 100644
--- a/erpnext/education/doctype/quiz_activity/quiz_activity.json
+++ b/erpnext/education/doctype/quiz_activity/quiz_activity.json
@@ -1,490 +1,163 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
+ "actions": [],
"autoname": "format:EDU-QA-{YYYY}-{#####}",
"beta": 1,
"creation": "2018-10-15 15:48:40.482821",
- "custom": 0,
- "docstatus": 0,
"doctype": "DocType",
- "document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
+ "field_order": [
+ "enrollment",
+ "student",
+ "column_break_3",
+ "course",
+ "section_break_5",
+ "quiz",
+ "column_break_7",
+ "status",
+ "section_break_9",
+ "result",
+ "section_break_11",
+ "activity_date",
+ "score",
+ "column_break_14",
+ "time_taken"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "enrollment",
"fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Enrollment",
- "length": 0,
- "no_copy": 0,
"options": "Course Enrollment",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 1,
- "translatable": 0,
- "unique": 0
+ "set_only_once": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fetch_from": "enrollment.student",
"fieldname": "student",
"fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "Student",
- "length": 0,
- "no_copy": 0,
"options": "Student",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "column_break_3",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldtype": "Column Break"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fetch_from": "enrollment.course",
"fieldname": "course",
"fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Course",
- "length": 0,
- "no_copy": 0,
"options": "Course",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
"read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 1,
- "translatable": 0,
- "unique": 0
+ "set_only_once": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "section_break_5",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldtype": "Section Break"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "quiz",
"fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "Quiz",
- "length": 0,
- "no_copy": 0,
"options": "Quiz",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 1,
- "translatable": 0,
- "unique": 0
+ "set_only_once": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "column_break_7",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldtype": "Column Break"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "status",
"fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Status",
- "length": 0,
- "no_copy": 0,
"options": "\nPass\nFail",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "section_break_9",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldtype": "Section Break"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "result",
"fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Result",
- "length": 0,
- "no_copy": 0,
"options": "Quiz Result",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 1,
- "translatable": 0,
- "unique": 0
+ "set_only_once": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "activity_date",
"fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Activity Date",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 1,
- "translatable": 0,
- "unique": 0
+ "set_only_once": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "score",
"fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "Score",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 1,
- "translatable": 0,
- "unique": 0
+ "set_only_once": 1
+ },
+ {
+ "fieldname": "time_taken",
+ "fieldtype": "Duration",
+ "label": "Time Taken",
+ "set_only_once": 1
+ },
+ {
+ "fieldname": "section_break_11",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "column_break_14",
+ "fieldtype": "Column Break"
}
],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-11-25 19:05:52.434437",
+ "links": [],
+ "modified": "2020-12-24 15:41:20.085380",
"modified_by": "Administrator",
"module": "Education",
"name": "Quiz Activity",
- "name_case": "",
"owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Academics User",
- "set_user_permissions": 0,
"share": 1,
- "submit": 0,
"write": 1
},
{
- "amend": 0,
- "cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "LMS User",
- "set_user_permissions": 0,
"share": 1,
- "submit": 0,
"write": 1
},
{
- "amend": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
"email": 1,
"export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Instructor",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 0
+ "share": 1
}
],
"quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/education/doctype/student/student.py b/erpnext/education/doctype/student/student.py
index 81626f1..2dc0f63 100644
--- a/erpnext/education/doctype/student/student.py
+++ b/erpnext/education/doctype/student/student.py
@@ -114,7 +114,7 @@
status = check_content_completion(content.name, content.doctype, course_enrollment_name)
progress.append({'content': content.name, 'content_type': content.doctype, 'is_complete': status})
elif content.doctype == 'Quiz':
- status, score, result = check_quiz_completion(content, course_enrollment_name)
+ status, score, result, time_taken = check_quiz_completion(content, course_enrollment_name)
progress.append({'content': content.name, 'content_type': content.doctype, 'is_complete': status, 'score': score, 'result': result})
return progress
diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py
index cffc396..8f51fef 100644
--- a/erpnext/education/utils.py
+++ b/erpnext/education/utils.py
@@ -194,7 +194,7 @@
return enrollment.add_activity(content_type, content)
@frappe.whitelist()
-def evaluate_quiz(quiz_response, quiz_name, course, program):
+def evaluate_quiz(quiz_response, quiz_name, course, program, time_taken):
import json
student = get_current_student()
@@ -209,7 +209,7 @@
if student:
enrollment = get_or_create_course_enrollment(course, program)
if quiz.allowed_attempt(enrollment, quiz_name):
- enrollment.add_quiz_activity(quiz_name, quiz_response, result, score, status)
+ enrollment.add_quiz_activity(quiz_name, quiz_response, result, score, status, time_taken)
return {'result': result, 'score': score, 'status': status}
else:
return None
@@ -219,8 +219,9 @@
try:
quiz = frappe.get_doc("Quiz", quiz_name)
questions = quiz.get_questions()
+ duration = quiz.duration
except:
- frappe.throw(_("Quiz {0} does not exist").format(quiz_name))
+ frappe.throw(_("Quiz {0} does not exist").format(quiz_name), frappe.DoesNotExistError)
return None
questions = [{
@@ -232,12 +233,20 @@
} for question in questions]
if has_super_access():
- return {'questions': questions, 'activity': None}
+ return {
+ 'questions': questions,
+ 'activity': None,
+ 'duration':duration
+ }
student = get_current_student()
course_enrollment = get_enrollment("course", course, student.name)
- status, score, result = check_quiz_completion(quiz, course_enrollment)
- return {'questions': questions, 'activity': {'is_complete': status, 'score': score, 'result': result}}
+ status, score, result, time_taken = check_quiz_completion(quiz, course_enrollment)
+ return {
+ 'questions': questions,
+ 'activity': {'is_complete': status, 'score': score, 'result': result, 'time_taken': time_taken},
+ 'duration': quiz.duration
+ }
def get_topic_progress(topic, course_name, program):
"""
@@ -361,15 +370,23 @@
return False
def check_quiz_completion(quiz, enrollment_name):
- attempts = frappe.get_all("Quiz Activity", filters={'enrollment': enrollment_name, 'quiz': quiz.name}, fields=["name", "activity_date", "score", "status"])
+ attempts = frappe.get_all("Quiz Activity",
+ filters={
+ 'enrollment': enrollment_name,
+ 'quiz': quiz.name
+ },
+ fields=["name", "activity_date", "score", "status", "time_taken"]
+ )
status = False if quiz.max_attempts == 0 else bool(len(attempts) >= quiz.max_attempts)
score = None
result = None
+ time_taken = None
if attempts:
if quiz.grading_basis == 'Last Highest Score':
attempts = sorted(attempts, key = lambda i: int(i.score), reverse=True)
score = attempts[0]['score']
result = attempts[0]['status']
+ time_taken = attempts[0]['time_taken']
if result == 'Pass':
status = True
- return status, score, result
\ No newline at end of file
+ return status, score, result, time_taken
\ No newline at end of file
diff --git a/erpnext/public/js/education/lms/quiz.js b/erpnext/public/js/education/lms/quiz.js
index 4a9d1e3..32fa4ab 100644
--- a/erpnext/public/js/education/lms/quiz.js
+++ b/erpnext/public/js/education/lms/quiz.js
@@ -20,6 +20,16 @@
}
make(data) {
+ if (data.duration) {
+ const timer_display = document.createElement("div");
+ timer_display.classList.add("lms-timer", "float-right", "font-weight-bold");
+ document.getElementsByClassName("lms-title")[0].appendChild(timer_display);
+ if (!data.activity || (data.activity && !data.activity.is_complete)) {
+ this.initialiseTimer(data.duration);
+ this.is_time_bound = true;
+ this.time_taken = 0;
+ }
+ }
data.questions.forEach(question_data => {
let question_wrapper = document.createElement('div');
let question = new Question({
@@ -37,12 +47,51 @@
indicator = 'green'
message = 'You have already cleared the quiz.'
}
-
+ if (data.activity.time_taken) {
+ this.calculate_and_display_time(data.activity.time_taken, "Time Taken - ");
+ }
this.set_quiz_footer(message, indicator, data.activity.score)
}
else {
this.make_actions();
}
+ window.addEventListener('beforeunload', (event) => {
+ event.preventDefault();
+ event.returnValue = '';
+ });
+ }
+
+ initialiseTimer(duration) {
+ this.time_left = duration;
+ var self = this;
+ var old_diff;
+ this.calculate_and_display_time(this.time_left, "Time Left - ");
+ this.start_time = new Date().getTime();
+ this.timer = setInterval(function () {
+ var diff = (new Date().getTime() - self.start_time)/1000;
+ var variation = old_diff ? diff - old_diff : diff;
+ old_diff = diff;
+ self.time_left -= variation;
+ self.time_taken += variation;
+ self.calculate_and_display_time(self.time_left, "Time Left - ");
+ if (self.time_left <= 0) {
+ clearInterval(self.timer);
+ self.time_taken -= 1;
+ self.submit();
+ }
+ }, 1000);
+ }
+
+ calculate_and_display_time(second, text) {
+ var timer_display = document.getElementsByClassName("lms-timer")[0];
+ var hours = this.append_zero(Math.floor(second / 3600));
+ var minutes = this.append_zero(Math.floor(second % 3600 / 60));
+ var seconds = this.append_zero(Math.ceil(second % 3600 % 60));
+ timer_display.innerText = text + hours + ":" + minutes + ":" + seconds;
+ }
+
+ append_zero(time) {
+ return time > 9 ? time : "0" + time;
}
make_actions() {
@@ -57,6 +106,10 @@
}
submit() {
+ if (this.is_time_bound) {
+ clearInterval(this.timer);
+ $(".lms-timer").text("");
+ }
this.submit_btn.innerText = 'Evaluating..'
this.submit_btn.disabled = true
this.disable()
@@ -64,7 +117,8 @@
quiz_name: this.name,
quiz_response: this.get_selected(),
course: this.course,
- program: this.program
+ program: this.program,
+ time_taken: this.is_time_bound ? this.time_taken : ""
}).then(res => {
this.submit_btn.remove()
if (!res.message) {
@@ -157,7 +211,7 @@
return input;
}
- let make_label = function(name, value) {
+ let make_label = function (name, value) {
let label = document.createElement('label');
label.classList.add('form-check-label');
label.htmlFor = name;
@@ -166,14 +220,14 @@
}
let make_option = function (wrapper, option) {
- let option_div = document.createElement('div')
- option_div.classList.add('form-check', 'pb-1')
+ let option_div = document.createElement('div');
+ option_div.classList.add('form-check', 'pb-1');
let input = make_input(option.name, option.option);
let label = make_label(option.name, option.option);
- option_div.appendChild(input)
- option_div.appendChild(label)
- wrapper.appendChild(option_div)
- return {input: input, ...option}
+ option_div.appendChild(input);
+ option_div.appendChild(label);
+ wrapper.appendChild(option_div);
+ return { input: input, ...option };
}
let options_wrapper = document.createElement('div')
diff --git a/erpnext/www/lms/content.html b/erpnext/www/lms/content.html
index dc9b6d8..15afb09 100644
--- a/erpnext/www/lms/content.html
+++ b/erpnext/www/lms/content.html
@@ -62,7 +62,7 @@
{{_('Back to Course')}}
</a>
</div>
- <div>
+ <div class="lms-title">
<h2>{{ content.name }} <span class="small text-muted">({{ position + 1 }}/{{length}})</span></h2>
</div>
{% endmacro %}
@@ -169,14 +169,51 @@
const next_url = '/lms/course?name={{ course }}&program={{ program }}'
{% endif %}
frappe.ready(() => {
- const quiz = new Quiz(document.getElementById('quiz-wrapper'), {
- name: '{{ content.name }}',
- course: '{{ course }}',
- program: '{{ program }}',
- quiz_exit_button: quiz_exit_button,
- next_url: next_url
- })
- window.quiz = quiz;
+ {% if content.is_time_bound %}
+ var duration = get_duration("{{content.duration}}")
+ var d = frappe.msgprint({
+ title: __('Important Notice'),
+ indicator: "red",
+ message: __(`This is a Time-Bound Quiz. <br><br>
+ A timer for <b>${duration}</b> will start, once you click on <b>Proceed</b>. <br><br>
+ If you fail to submit before the time is up, the Quiz will be submitted automatically.`),
+ primary_action: {
+ label: __("Proceed"),
+ action: () => {
+ create_quiz();
+ d.hide();
+ }
+ },
+ secondary_action: {
+ action: () => {
+ d.hide();
+ window.location.href = "/lms/course?name={{ course }}&program={{ program }}";
+ },
+ label: __("Go Back"),
+ }
+ });
+ {% else %}
+ create_quiz();
+ {% endif %}
+ function create_quiz() {
+ const quiz = new Quiz(document.getElementById('quiz-wrapper'), {
+ name: '{{ content.name }}',
+ course: '{{ course }}',
+ program: '{{ program }}',
+ quiz_exit_button: quiz_exit_button,
+ next_url: next_url
+ })
+ window.quiz = quiz;
+ }
+ function get_duration(seconds){
+ var hours = append_zero(Math.floor(seconds / 3600));
+ var minutes = append_zero(Math.floor(seconds % 3600 / 60));
+ var seconds = append_zero(Math.floor(seconds % 3600 % 60));
+ return `${hours}:${minutes}:${seconds}`;
+ }
+ function append_zero(time) {
+ return time > 9 ? time : "0" + time;
+ }
})
{% endif %}
diff --git a/erpnext/www/lms/index.html b/erpnext/www/lms/index.html
index 7b239ac..c1e9620 100644
--- a/erpnext/www/lms/index.html
+++ b/erpnext/www/lms/index.html
@@ -42,7 +42,9 @@
<section class="top-section" style="padding: 6rem 0rem;">
<div class='container pb-5'>
<h1>{{ education_settings.portal_title }}</h1>
- <p class='lead'>{{ education_settings.description }}</p>
+ {% if education_settings.description %}
+ <p class='lead'>{{ education_settings.description }}</p>
+ {% endif %}
<p class="mt-4">
{% if frappe.session.user == 'Guest' %}
<a class="btn btn-primary btn-lg" href="/login#signup">{{_('Sign Up')}}</a>
@@ -51,13 +53,15 @@
</div>
<div class='container'>
<div class="row mt-5">
- {% for program in featured_programs %}
- {{ program_card(program.program, program.has_access) }}
- {% endfor %}
{% if featured_programs %}
+ {% for program in featured_programs %}
+ {{ program_card(program.program, program.has_access) }}
+ {% endfor %}
{% for n in range( (3 - (featured_programs|length)) %3) %}
{{ null_card() }}
{% endfor %}
+ {% else %}
+ <p class="lead">You have not enrolled in any program. Contact your Instructor.</p>
{% endif %}
</div>
</div>
diff --git a/erpnext/www/lms/topic.py b/erpnext/www/lms/topic.py
index f75ae8e..8abbc72 100644
--- a/erpnext/www/lms/topic.py
+++ b/erpnext/www/lms/topic.py
@@ -35,7 +35,7 @@
progress.append({'content': content, 'content_type': content.doctype, 'completed': status})
elif content.doctype == 'Quiz':
if student:
- status, score, result = utils.check_quiz_completion(content, course_enrollment.name)
+ status, score, result, time_taken = utils.check_quiz_completion(content, course_enrollment.name)
else:
status = False
score = None