refactor: refactored quiz api and added quiz.js
This commit is contained in:
parent
46b3446da0
commit
d1a252190b
@ -35,7 +35,7 @@ class CourseEnrollment(Document):
|
|||||||
if enrollment:
|
if enrollment:
|
||||||
frappe.throw(_("Student is already enrolled."))
|
frappe.throw(_("Student is already enrolled."))
|
||||||
|
|
||||||
def add_quiz_activity(self, quiz_name, quiz_response,answers, score, status):
|
def add_quiz_activity(self, quiz_name, quiz_response, answers, score, status):
|
||||||
result = {k: ('Correct' if v else 'Wrong') for k,v in answers.items()}
|
result = {k: ('Correct' if v else 'Wrong') for k,v in answers.items()}
|
||||||
result_data = []
|
result_data = []
|
||||||
for key in answers:
|
for key in answers:
|
||||||
@ -43,7 +43,9 @@ class CourseEnrollment(Document):
|
|||||||
item['question'] = key
|
item['question'] = key
|
||||||
item['quiz_result'] = result[key]
|
item['quiz_result'] = result[key]
|
||||||
try:
|
try:
|
||||||
if isinstance(quiz_response[key], list):
|
if not quiz_response[key]:
|
||||||
|
item['selected_option'] = "Unattempted"
|
||||||
|
elif isinstance(quiz_response[key], list):
|
||||||
item['selected_option'] = ', '.join(frappe.get_value('Options', res, 'option') for res in quiz_response[key])
|
item['selected_option'] = ', '.join(frappe.get_value('Options', res, 'option') for res in quiz_response[key])
|
||||||
else:
|
else:
|
||||||
item['selected_option'] = frappe.get_value('Options', quiz_response[key], 'option')
|
item['selected_option'] = frappe.get_value('Options', quiz_response[key], 'option')
|
||||||
@ -59,7 +61,7 @@ class CourseEnrollment(Document):
|
|||||||
"result": result_data,
|
"result": result_data,
|
||||||
"score": score,
|
"score": score,
|
||||||
"status": status
|
"status": status
|
||||||
}).insert()
|
}).insert(ignore_permissions = True)
|
||||||
|
|
||||||
def add_activity(self, content_type, content):
|
def add_activity(self, content_type, content):
|
||||||
activity = check_activity_exists(self.name, content_type, content)
|
activity = check_activity_exists(self.name, content_type, content)
|
||||||
|
|||||||
@ -11,50 +11,43 @@ class Quiz(Document):
|
|||||||
if self.passing_score > 100:
|
if self.passing_score > 100:
|
||||||
frappe.throw("Passing Score value should be between 0 and 100")
|
frappe.throw("Passing Score value should be between 0 and 100")
|
||||||
|
|
||||||
def validate_quiz_attempts(self, enrollment, quiz_name):
|
def allowed_attempt(self, enrollment, quiz_name):
|
||||||
if self.max_attempts > 0:
|
if self.max_attempts == 0:
|
||||||
try:
|
return True
|
||||||
if len(frappe.get_all("Quiz Activity", {'enrollment': enrollment.name, 'quiz': quiz_name})) >= self.max_attempts:
|
|
||||||
frappe.throw('Maximum attempts reached!')
|
try:
|
||||||
except Exception as e:
|
if len(frappe.get_all("Quiz Activity", {'enrollment': enrollment.name, 'quiz': quiz_name})) >= self.max_attempts:
|
||||||
pass
|
frappe.msgprint("Maximum attempts for this quiz reached!")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def evaluate(self, response_dict, quiz_name):
|
def evaluate(self, response_dict, quiz_name):
|
||||||
# self.validate_quiz_attempts(enrollment, quiz_name)
|
|
||||||
questions = [frappe.get_doc('Question', question.question_link) for question in self.question]
|
questions = [frappe.get_doc('Question', question.question_link) for question in self.question]
|
||||||
answers = {q.name:q.get_answer() for q in questions}
|
answers = {q.name:q.get_answer() for q in questions}
|
||||||
correct_answers = {}
|
result = {}
|
||||||
for key in answers:
|
for key in answers:
|
||||||
try:
|
try:
|
||||||
if isinstance(response_dict[key], list):
|
if isinstance(response_dict[key], list):
|
||||||
result = compare_list_elementwise(response_dict[key], answers[key])
|
is_correct = compare_list_elementwise(response_dict[key], answers[key])
|
||||||
else:
|
else:
|
||||||
result = (response_dict[key] == answers[key])
|
is_correct = (response_dict[key] == answers[key])
|
||||||
except:
|
except Exception as e:
|
||||||
result = False
|
is_correct = False
|
||||||
correct_answers[key] = result
|
result[key] = is_correct
|
||||||
score = (sum(correct_answers.values()) * 100 ) / len(answers)
|
score = (sum(result.values()) * 100 ) / len(answers)
|
||||||
if score >= self.passing_score:
|
if score >= self.passing_score:
|
||||||
status = "Pass"
|
status = "Pass"
|
||||||
else:
|
else:
|
||||||
status = "Fail"
|
status = "Fail"
|
||||||
return correct_answers, score, status
|
return result, score, status
|
||||||
|
|
||||||
|
|
||||||
def get_questions(self):
|
def get_questions(self):
|
||||||
quiz_question = self.get_all_children()
|
return [frappe.get_doc('Question', question.question_link) for question in self.question]
|
||||||
if quiz_question:
|
|
||||||
questions = [frappe.get_doc('Question', question.question_link).as_dict() for question in quiz_question]
|
|
||||||
for question in questions:
|
|
||||||
correct_options = [option.is_correct for option in question.options]
|
|
||||||
if sum(correct_options) > 1:
|
|
||||||
question['type'] = "MultipleChoice"
|
|
||||||
else:
|
|
||||||
question['type'] = "SingleChoice"
|
|
||||||
return questions
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def compare_list_elementwise(*args):
|
def compare_list_elementwise(*args):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -1,145 +1,52 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"creation": "2018-10-15 15:52:25.766374",
|
||||||
"allow_events_in_timeline": 0,
|
"doctype": "DocType",
|
||||||
"allow_guest_to_view": 0,
|
"editable_grid": 1,
|
||||||
"allow_import": 0,
|
"engine": "InnoDB",
|
||||||
"allow_rename": 0,
|
"field_order": [
|
||||||
"beta": 0,
|
"question",
|
||||||
"creation": "2018-10-15 15:52:25.766374",
|
"selected_option",
|
||||||
"custom": 0,
|
"quiz_result"
|
||||||
"docstatus": 0,
|
],
|
||||||
"doctype": "DocType",
|
|
||||||
"document_type": "",
|
|
||||||
"editable_grid": 1,
|
|
||||||
"engine": "InnoDB",
|
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "question",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Link",
|
||||||
"allow_on_submit": 0,
|
"in_list_view": 1,
|
||||||
"bold": 0,
|
"label": "Question",
|
||||||
"collapsible": 0,
|
"options": "Question",
|
||||||
"columns": 0,
|
"read_only": 1,
|
||||||
"fetch_if_empty": 0,
|
"reqd": 1,
|
||||||
"fieldname": "question",
|
"set_only_once": 1
|
||||||
"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": "Question",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Question",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 1,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "selected_option",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Data",
|
||||||
"allow_on_submit": 0,
|
"in_list_view": 1,
|
||||||
"bold": 0,
|
"label": "Selected Option",
|
||||||
"collapsible": 0,
|
"read_only": 1,
|
||||||
"columns": 0,
|
"set_only_once": 1
|
||||||
"fetch_if_empty": 0,
|
},
|
||||||
"fieldname": "selected_option",
|
|
||||||
"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": "Selected Option",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 1,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "quiz_result",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Select",
|
||||||
"allow_on_submit": 0,
|
"in_list_view": 1,
|
||||||
"bold": 0,
|
"label": "Result",
|
||||||
"collapsible": 0,
|
"options": "\nCorrect\nWrong",
|
||||||
"columns": 0,
|
"read_only": 1,
|
||||||
"fetch_if_empty": 0,
|
"reqd": 1,
|
||||||
"fieldname": "quiz_result",
|
"set_only_once": 1
|
||||||
"fieldtype": "Select",
|
|
||||||
"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": "Result",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "\nCorrect\nWrong",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 1,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
"istable": 1,
|
||||||
"hide_heading": 0,
|
"modified": "2019-06-03 12:52:32.267392",
|
||||||
"hide_toolbar": 0,
|
"modified_by": "Administrator",
|
||||||
"idx": 0,
|
"module": "Education",
|
||||||
"image_view": 0,
|
"name": "Quiz Result",
|
||||||
"in_create": 0,
|
"owner": "Administrator",
|
||||||
"is_submittable": 0,
|
"permissions": [],
|
||||||
"issingle": 0,
|
"quick_entry": 1,
|
||||||
"istable": 1,
|
"sort_field": "modified",
|
||||||
"max_attachments": 0,
|
"sort_order": "DESC",
|
||||||
"modified": "2019-03-27 17:58:54.388848",
|
"track_changes": 1
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Education",
|
|
||||||
"name": "Quiz Result",
|
|
||||||
"name_case": "",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [],
|
|
||||||
"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
|
|
||||||
}
|
}
|
||||||
@ -1,6 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2015, Frappe Technologies and contributors
|
# Copyright (c) 2015, Frappe Technologies and contributors
|
||||||
# For lice
|
|
||||||
|
|
||||||
from __future__ import unicode_literals, division
|
from __future__ import unicode_literals, division
|
||||||
import frappe
|
import frappe
|
||||||
@ -173,7 +172,7 @@ def has_super_access():
|
|||||||
"""Check if user has a role that allows full access to LMS
|
"""Check if user has a role that allows full access to LMS
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: true if user has access to all lms content
|
bool: true if user has access to all lms content
|
||||||
"""
|
"""
|
||||||
current_user = frappe.get_doc('User', frappe.session.user)
|
current_user = frappe.get_doc('User', frappe.session.user)
|
||||||
roles = set([role.role for role in current_user.roles])
|
roles = set([role.role for role in current_user.roles])
|
||||||
@ -189,7 +188,6 @@ def add_activity(course, content_type, content):
|
|||||||
return frappe.throw("Student with email {0} does not exist".format(frappe.session.user), frappe.DoesNotExistError)
|
return frappe.throw("Student with email {0} does not exist".format(frappe.session.user), frappe.DoesNotExistError)
|
||||||
|
|
||||||
course_enrollment = get_enrollment("course", course, student.name)
|
course_enrollment = get_enrollment("course", course, student.name)
|
||||||
print(course_enrollment)
|
|
||||||
if not course_enrollment:
|
if not course_enrollment:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -199,6 +197,56 @@ def add_activity(course, content_type, content):
|
|||||||
else:
|
else:
|
||||||
return enrollment.add_activity(content_type, content)
|
return enrollment.add_activity(content_type, content)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def evaluate_quiz(quiz_response, quiz_name, course):
|
||||||
|
import json
|
||||||
|
|
||||||
|
student = get_current_student()
|
||||||
|
|
||||||
|
quiz_response = json.loads(quiz_response)
|
||||||
|
quiz = frappe.get_doc("Quiz", quiz_name)
|
||||||
|
result, score, status = quiz.evaluate(quiz_response, quiz_name)
|
||||||
|
|
||||||
|
if has_super_access():
|
||||||
|
return {'result': result, 'score': score, 'status': status}
|
||||||
|
|
||||||
|
if student:
|
||||||
|
course_enrollment = get_enrollment("course", course, student.name)
|
||||||
|
if course_enrollment:
|
||||||
|
enrollment = frappe.get_doc('Course Enrollment', course_enrollment)
|
||||||
|
if quiz.allowed_attempt(enrollment, quiz_name):
|
||||||
|
enrollment.add_quiz_activity(quiz_name, quiz_response, result, score, status)
|
||||||
|
return {'result': result, 'score': score, 'status': status}
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
frappe.throw("Something went wrong. Pleae contact the administrator.")
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_quiz(quiz_name, course):
|
||||||
|
try:
|
||||||
|
quiz = frappe.get_doc("Quiz", quiz_name)
|
||||||
|
questions = quiz.get_questions()
|
||||||
|
except:
|
||||||
|
frappe.throw("Quiz {0} does not exist".format(quiz_name))
|
||||||
|
return None
|
||||||
|
|
||||||
|
questions = [{
|
||||||
|
'name': question.name,
|
||||||
|
'question': question.question,
|
||||||
|
'type': question.question_type,
|
||||||
|
'options': [{'name': option.name, 'option': option.option}
|
||||||
|
for option in question.options],
|
||||||
|
} for question in questions]
|
||||||
|
|
||||||
|
if has_super_access():
|
||||||
|
return {'questions': questions, 'activity': None}
|
||||||
|
|
||||||
|
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}}
|
||||||
|
|
||||||
def create_student_from_current_user():
|
def create_student_from_current_user():
|
||||||
user = frappe.get_doc("User", frappe.session.user)
|
user = frappe.get_doc("User", frappe.session.user)
|
||||||
|
|
||||||
@ -226,7 +274,7 @@ def check_content_completion(content_name, content_type, enrollment_name):
|
|||||||
|
|
||||||
def check_quiz_completion(quiz, enrollment_name):
|
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"])
|
||||||
status = False if quiz.max_attempts == 0 else bool(len(attempts) == quiz.max_attempts)
|
status = False if quiz.max_attempts == 0 else bool(len(attempts) >= quiz.max_attempts)
|
||||||
score = None
|
score = None
|
||||||
result = None
|
result = None
|
||||||
if attempts:
|
if attempts:
|
||||||
|
|||||||
185
erpnext/public/js/education/lms/quiz.js
Normal file
185
erpnext/public/js/education/lms/quiz.js
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
class Quiz {
|
||||||
|
constructor(wrapper, options) {
|
||||||
|
this.wrapper = wrapper;
|
||||||
|
Object.assign(this, options);
|
||||||
|
this.questions = []
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh() {
|
||||||
|
this.get_quiz();
|
||||||
|
}
|
||||||
|
|
||||||
|
get_quiz() {
|
||||||
|
frappe.call('erpnext.education.utils.get_quiz', {
|
||||||
|
quiz_name: this.name,
|
||||||
|
course: this.course
|
||||||
|
}).then(res => {
|
||||||
|
this.make(res.message)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
make(data) {
|
||||||
|
data.questions.forEach(question_data => {
|
||||||
|
let question_wrapper = document.createElement('div');
|
||||||
|
let question = new Question({
|
||||||
|
wrapper: question_wrapper,
|
||||||
|
...question_data
|
||||||
|
});
|
||||||
|
this.questions.push(question)
|
||||||
|
this.wrapper.appendChild(question_wrapper);
|
||||||
|
})
|
||||||
|
if (data.activity.is_complete) {
|
||||||
|
this.disable()
|
||||||
|
let indicator = 'red'
|
||||||
|
let message = 'Your are not allowed to attempt the quiz again.'
|
||||||
|
if (data.activity.result == 'Pass') {
|
||||||
|
indicator = 'green'
|
||||||
|
message = 'You have already cleared the quiz.'
|
||||||
|
}
|
||||||
|
|
||||||
|
this.set_quiz_footer(message, indicator, data.activity.score)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.make_actions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
make_actions() {
|
||||||
|
const button = document.createElement("button");
|
||||||
|
button.classList.add("btn", "btn-primary", "mt-5", "mr-2");
|
||||||
|
|
||||||
|
button.id = 'submit-button';
|
||||||
|
button.innerText = 'Submit';
|
||||||
|
button.onclick = () => this.submit();
|
||||||
|
this.submit_btn = button
|
||||||
|
this.wrapper.appendChild(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
submit() {
|
||||||
|
this.submit_btn.innerText = 'Evaluating..'
|
||||||
|
this.submit_btn.disabled = true
|
||||||
|
this.disable()
|
||||||
|
frappe.call('erpnext.education.utils.evaluate_quiz', {
|
||||||
|
quiz_name: this.name,
|
||||||
|
quiz_response: this.get_selected(),
|
||||||
|
course: this.course
|
||||||
|
}).then(res => {
|
||||||
|
this.submit_btn.remove()
|
||||||
|
if (!res.message) {
|
||||||
|
frappe.throw("Something went wrong while evaluating the quiz.")
|
||||||
|
}
|
||||||
|
|
||||||
|
let indicator = 'red'
|
||||||
|
let message = 'Fail'
|
||||||
|
if (res.message.status == 'Pass') {
|
||||||
|
indicator = 'green'
|
||||||
|
message = 'Congratulations, you cleared the quiz.'
|
||||||
|
}
|
||||||
|
|
||||||
|
this.set_quiz_footer(message, indicator, res.message.score)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
set_quiz_footer(message, indicator, score) {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.classList.add("mt-5");
|
||||||
|
div.innerHTML = `<div class="row">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<h4>${message}</h4>
|
||||||
|
<h5 class="text-muted"><span class="indicator ${indicator}">Score: ${score}/100</span></h5>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<a href="${this.next_url}" class="btn btn-primary pull-right">${this.quiz_exit_button}</a>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
|
||||||
|
this.wrapper.appendChild(div)
|
||||||
|
}
|
||||||
|
|
||||||
|
disable() {
|
||||||
|
this.questions.forEach(que => que.disable())
|
||||||
|
}
|
||||||
|
|
||||||
|
get_selected() {
|
||||||
|
let que = {}
|
||||||
|
this.questions.forEach(question => {
|
||||||
|
que[question.name] = question.get_selected()
|
||||||
|
})
|
||||||
|
return que
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Question {
|
||||||
|
constructor(opts) {
|
||||||
|
Object.assign(this, opts);
|
||||||
|
this.make();
|
||||||
|
}
|
||||||
|
|
||||||
|
make() {
|
||||||
|
this.make_question()
|
||||||
|
this.make_options()
|
||||||
|
}
|
||||||
|
|
||||||
|
get_selected() {
|
||||||
|
let selected = this.options.filter(opt => opt.input.checked)
|
||||||
|
if (this.type == 'Single Correct Answer') {
|
||||||
|
if (selected[0]) return selected[0].name
|
||||||
|
}
|
||||||
|
if (this.type == 'Multiple Correct Answer') {
|
||||||
|
return selected.map(opt => opt.name)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
disable() {
|
||||||
|
let selected = this.options.forEach(opt => opt.input.disabled = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
make_question() {
|
||||||
|
let question_wrapper = document.createElement('h5');
|
||||||
|
question_wrapper.classList.add('mt-3');
|
||||||
|
question_wrapper.innerText = this.question;
|
||||||
|
this.wrapper.appendChild(question_wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
make_options() {
|
||||||
|
let make_input = (name, value) => {
|
||||||
|
let input = document.createElement('input');
|
||||||
|
input.id = name;
|
||||||
|
input.name = this.name;
|
||||||
|
input.value = value;
|
||||||
|
input.type = 'radio';
|
||||||
|
if (this.type == 'Multiple Correct Answer')
|
||||||
|
input.type = 'checkbox';
|
||||||
|
input.classList.add('form-check-input');
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
let make_label = function(name, value) {
|
||||||
|
let label = document.createElement('label');
|
||||||
|
label.classList.add('form-check-label');
|
||||||
|
label.htmlFor = name;
|
||||||
|
label.innerText = value;
|
||||||
|
return label
|
||||||
|
}
|
||||||
|
|
||||||
|
let make_option = function (wrapper, option) {
|
||||||
|
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}
|
||||||
|
}
|
||||||
|
|
||||||
|
let options_wrapper = document.createElement('div')
|
||||||
|
options_wrapper.classList.add('ml-2')
|
||||||
|
let option_list = []
|
||||||
|
this.options.forEach(opt => option_list.push(make_option(options_wrapper, opt)))
|
||||||
|
this.options = option_list
|
||||||
|
this.wrapper.appendChild(options_wrapper)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -36,7 +36,7 @@
|
|||||||
<div class="col-md-7">
|
<div class="col-md-7">
|
||||||
<h1>{{ content.name }} <span class="small text-muted">({{ position + 1 }}/{{length}})</span></h1>
|
<h1>{{ content.name }} <span class="small text-muted">({{ position + 1 }}/{{length}})</span></h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-5 text-right">
|
<div id="nav-buttons" class="col-md-5 text-right" {{ 'hidden' if content_type=='Quiz' }}>
|
||||||
{% if previous %}
|
{% if previous %}
|
||||||
<a href="/lms/content?program={{ program }}&course={{ course }}&topic={{ topic }}&type={{ previous.content_type }}&content={{ previous.content }}" class='btn btn-outline-secondary'>Previous</a>
|
<a href="/lms/content?program={{ program }}&course={{ course }}&topic={{ topic }}&type={{ previous.content_type }}&content={{ previous.content }}" class='btn btn-outline-secondary'>Previous</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -70,7 +70,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="player" data-plyr-provider="{{ content.provider|lower }}" data-plyr-embed-id="{{ content.url }}"></div>
|
<div id="player" data-plyr-provider="{{ content.provider|lower }}" data-plyr-embed-id="{{ content.url }}"></div>
|
||||||
<div class="my-5">
|
<div class="my-5" style="line-height: 1.8em;">
|
||||||
{{ content.description }}
|
{{ content.description }}
|
||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
@ -95,6 +95,18 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro quiz() %}
|
||||||
|
<div class="mb-5">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-7">
|
||||||
|
<h1>{{ content.name }} <span class="small text-muted">({{ position + 1 }}/{{length}})</span></h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="quiz-wrapper">
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<div>
|
<div>
|
||||||
@ -104,7 +116,7 @@
|
|||||||
{% elif content_type=='Article'%}
|
{% elif content_type=='Article'%}
|
||||||
{{ article() }}
|
{{ article() }}
|
||||||
{% elif content_type=='Quiz' %}
|
{% elif content_type=='Quiz' %}
|
||||||
<h2>Quiz: {{ content.name }}</h2>
|
{{ quiz() }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -113,20 +125,41 @@
|
|||||||
|
|
||||||
{% block script %}
|
{% block script %}
|
||||||
{% if content_type=='Video' %}
|
{% if content_type=='Video' %}
|
||||||
<script src="https://cdn.plyr.io/3.5.3/plyr.js"></script>
|
<script src="https://cdn.plyr.io/3.5.3/plyr.js"></script>
|
||||||
|
{% elif content_type == 'Quiz' %}
|
||||||
|
<script src='/assets/erpnext/js/education/lms/quiz.js'></script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<script>
|
<script>
|
||||||
{% if content_type=='Video' %}
|
{% if content_type == 'Video' %}
|
||||||
const player = new Plyr('#player');
|
const player = new Plyr('#player');
|
||||||
|
{% elif content_type == 'Quiz' %}
|
||||||
|
{% if next %}
|
||||||
|
const quiz_exit_button = 'Next'
|
||||||
|
const next_url = '/lms/content?program={{ program }}&course={{ course }}&topic={{ topic }}&type={{ next.content_type }}&content={{ next.content }}'
|
||||||
|
{% else %}
|
||||||
|
const quiz_exit_button = 'Finish Course'
|
||||||
|
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 }}',
|
||||||
|
quiz_exit_button: quiz_exit_button,
|
||||||
|
next_url: next_url
|
||||||
|
})
|
||||||
|
window.quiz = quiz;
|
||||||
|
})
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if content_type != 'Quiz' %}
|
||||||
|
|
||||||
frappe.ready(() => {
|
frappe.ready(() => {
|
||||||
next = document.getElementById('nextButton')
|
next = document.getElementById('nextButton')
|
||||||
next.disabled = false;
|
next.disabled = false;
|
||||||
})
|
})
|
||||||
|
|
||||||
function handle(url) {
|
|
||||||
|
|
||||||
|
function handle(url) {
|
||||||
opts = {
|
opts = {
|
||||||
method: "erpnext.education.utils.add_activity",
|
method: "erpnext.education.utils.add_activity",
|
||||||
args: {
|
args: {
|
||||||
@ -139,5 +172,7 @@
|
|||||||
window.location.href = url;
|
window.location.href = url;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -27,7 +27,7 @@ def get_context(context):
|
|||||||
|
|
||||||
|
|
||||||
# Set context for content to be displayer
|
# Set context for content to be displayer
|
||||||
context.content = frappe.get_doc(content_type, content)
|
context.content = frappe.get_doc(content_type, content).as_dict()
|
||||||
context.content_type = content_type
|
context.content_type = content_type
|
||||||
context.program = program
|
context.program = program
|
||||||
context.course = course
|
context.course = course
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user