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>
This commit is contained in:
Jannat Patel
2021-04-19 10:36:40 +05:30
committed by GitHub
parent 7eac4a250d
commit dcdd3bebbe
9 changed files with 218 additions and 411 deletions

View File

@@ -41,7 +41,7 @@ class CourseEnrollment(Document):
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 @@ class CourseEnrollment(Document):
"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):

View File

@@ -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",

View File

@@ -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
}

View File

@@ -114,7 +114,7 @@ class Student(Document):
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

View File

@@ -194,7 +194,7 @@ def add_activity(course, content_type, content, program):
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 @@ def evaluate_quiz(quiz_response, quiz_name, course, program):
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 @@ def get_quiz(quiz_name, course):
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 @@ def get_quiz(quiz_name, course):
} 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 @@ def check_content_completion(content_name, content_type, enrollment_name):
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
return status, score, result, time_taken