blob: c106a2a76881f339e310b8e6d70ceced68a2a27d [file] [log] [blame]
scmmishra35bf5612018-11-13 16:36:22 +05301from __future__ import unicode_literals
scmmishrababb68d2018-11-19 16:13:21 +05302import erpnext.education.utils as utils
scmmishra35bf5612018-11-13 16:36:22 +05303import frappe
4
scmmishra11925292018-11-20 17:38:01 +05305# LMS Utils to Update State for Vue Store
6@frappe.whitelist()
7def get_program_enrollments():
8 try:
9 student = frappe.get_doc("Student", utils.get_current_student())
10 return student.get_program_enrollments()
11 except:
12 return None
13
14@frappe.whitelist()
15def get_all_course_enrollments():
16 student = utils.get_current_student()
17 if student == None:
18 return None
19 try:
20 student = frappe.get_doc("Student", student)
21 return student.get_all_course_enrollments()
22 except:
23 return None
24
25# Vue Client Functions
scmmishra35bf5612018-11-13 16:36:22 +053026@frappe.whitelist(allow_guest=True)
27def get_portal_details():
scmmishrababb68d2018-11-19 16:13:21 +053028 """
29 Returns portal details from Education Settings Doctype. This contains the Title and Description for LMS amoung other things.
30 """
scmmishra35bf5612018-11-13 16:36:22 +053031 settings = frappe.get_doc("Education Settings")
32 title = settings.portal_title
33 description = settings.description
34 return dict(title=title, description=description)
35
scmmishra35bf5612018-11-13 16:36:22 +053036@frappe.whitelist(allow_guest=True)
37def get_featured_programs():
38 featured_program_names = frappe.get_all("Program", filters={"is_published": True, "is_featured": True})
39 if featured_program_names:
scmmishrababb68d2018-11-19 16:13:21 +053040 featured_list = [utils.get_program(program['name']) for program in featured_program_names]
scmmishra35bf5612018-11-13 16:36:22 +053041 return featured_list
42 else:
43 return None
44
scmmishra85c2fee2018-11-14 14:23:06 +053045@frappe.whitelist(allow_guest=True)
46def get_all_programs():
47 program_names = frappe.get_all("Program", filters={"is_published": True})
48 if program_names:
scmmishrababb68d2018-11-19 16:13:21 +053049 featured_list = [utils.get_program(program['name']) for program in program_names]
scmmishra85c2fee2018-11-14 14:23:06 +053050 return featured_list
51 else:
52 return None
53
scmmishra35bf5612018-11-13 16:36:22 +053054@frappe.whitelist(allow_guest=True)
55def get_program_details(program_name):
56 try:
57 program = frappe.get_doc('Program', program_name)
58 return program
59 except:
60 return None
61
scmmishra35bf5612018-11-13 16:36:22 +053062# Functions to get program & course details
63@frappe.whitelist(allow_guest=True)
64def get_courses(program_name):
65 program = frappe.get_doc('Program', program_name)
66 courses = program.get_course_list()
scmmishrafdbabde2018-11-22 15:33:30 +053067 return courses
scmmishra35bf5612018-11-13 16:36:22 +053068
scmmishra35bf5612018-11-13 16:36:22 +053069@frappe.whitelist()
70def get_next_content(content, content_type, course):
71 if frappe.session.user == "Guest":
72 return None
73 course_doc = frappe.get_doc("Course", course)
74 content_list = [{'content_type':item.content_type, 'content':item.content} for item in course_doc.get_all_children()]
75 current_index = content_list.index({'content': content, 'content_type': content_type})
76 try:
scmmishra11925292018-11-20 17:38:01 +053077 # print(content_list[current_index + 1])
scmmishra35bf5612018-11-13 16:36:22 +053078 return content_list[current_index + 1]
79 except IndexError:
80 return None
81
82def get_quiz_with_answers(quiz_name):
83 try:
84 quiz = frappe.get_doc("Quiz", quiz_name).get_questions()
85 quiz_output = [{'name':question.name, 'question':question.question, 'options':[{'name': option.name, 'option':option.option, 'is_correct':option.is_correct} for option in question.options]} for question in quiz]
86 return quiz_output
87 except:
88 frappe.throw("Quiz {0} does not exist".format(quiz_name))
89 return None
90
91@frappe.whitelist()
92def get_quiz_without_answers(quiz_name):
93 try:
94 quiz = frappe.get_doc("Quiz", quiz_name).get_questions()
95 quiz_output = [{'name':question.name, 'question':question.question, 'options':[{'name': option.name, 'option':option.option} for option in question.options]} for question in quiz]
96 return quiz_output
97 except:
98 frappe.throw("Quiz {0} does not exist".format(quiz_name))
99 return None
100
101@frappe.whitelist()
102def evaluate_quiz(enrollment, quiz_response, quiz_name):
103 """LMS Function: Evaluates a simple multiple choice quiz.
scmmishra35bf5612018-11-13 16:36:22 +0530104 :param quiz_response: contains user selected choices for a quiz in the form of a string formatted as a dictionary. The function uses `json.loads()` to convert it to a python dictionary.
105 """
scmmishrababb68d2018-11-19 16:13:21 +0530106
scmmishra35bf5612018-11-13 16:36:22 +0530107 import json
108 quiz_response = json.loads(quiz_response)
109 quiz = frappe.get_doc("Quiz", quiz_name)
110 answers, score, status = quiz.evaluate(quiz_response, quiz_name)
111
112 result = {k: ('Correct' if v else 'Wrong') for k,v in answers.items()}
113 result_data = []
114 for key in answers:
115 item = {}
116 item['question'] = key
117 item['quiz_result'] = result[key]
118 try:
119 item['selected_option'] = frappe.get_value('Options', quiz_response[key], 'option')
120 except:
121 item['selected_option'] = "Unattempted"
122 result_data.append(item)
123 # result_data = [{'question': key, 'selected_option': frappe.get_value('Options', quiz_response[key], 'option'), 'quiz_result': result[key]} for key in answers]
124
125 add_quiz_activity(enrollment, quiz_name, result_data, score, status)
126 return(score)
127
scmmishrababb68d2018-11-19 16:13:21 +0530128def add_quiz_activity(enrollment, quiz_name, result_data, score, status):
129 quiz_activity = frappe.get_doc({
130 "doctype": "Quiz Activity",
131 "enrollment": enrollment,
132 "quiz": quiz_name,
133 "activity_date": frappe.utils.datetime.datetime.now(),
134 "result": result_data,
135 "score": score,
136 "status": status
137 })
138 quiz_activity.save()
139 frappe.db.commit()
scmmishra35bf5612018-11-13 16:36:22 +0530140
141@frappe.whitelist()
scmmishra35bf5612018-11-13 16:36:22 +0530142def enroll_in_program(program_name):
scmmishrababb68d2018-11-19 16:13:21 +0530143 if(not utils.get_current_student()):
scmmishra082e3c92018-11-23 17:16:22 +0530144 utils.create_student()
scmmishrababb68d2018-11-19 16:13:21 +0530145 student = frappe.get_doc("Student", utils.get_current_student())
scmmishra35bf5612018-11-13 16:36:22 +0530146 program_enrollment = student.enroll_in_program(program_name)
scmmishrababb68d2018-11-19 16:13:21 +0530147 utils.enroll_all_courses_in_program(program_enrollment, student)
scmmishra0a4902f2018-11-15 11:16:53 +0530148 return program_name
scmmishra35bf5612018-11-13 16:36:22 +0530149
scmmishra35bf5612018-11-13 16:36:22 +0530150# Academty Activity
151@frappe.whitelist()
scmmishraa592f702018-11-20 18:37:01 +0530152def add_activity(course, content_type, content):
153 enrollment = utils.get_course_enrollment(course)
154 if(utils.check_activity_exists(enrollment.name, content_type, content)):
scmmishra35bf5612018-11-13 16:36:22 +0530155 pass
156 else:
157 activity = frappe.get_doc({
158 "doctype": "Course Activity",
scmmishraa592f702018-11-20 18:37:01 +0530159 "enrollment": enrollment.name,
scmmishra35bf5612018-11-13 16:36:22 +0530160 "content_type": content_type,
161 "content": content,
162 "activity_date": frappe.utils.datetime.datetime.now()
163 })
164 activity.save()
165 frappe.db.commit()
166
scmmishrababb68d2018-11-19 16:13:21 +0530167def get_course_progress(course_enrollment):
168 course = frappe.get_doc('Course', course_enrollment.course)
scmmishra11925292018-11-20 17:38:01 +0530169 contents = course.get_contents()
170 progress = []
scmmishrafdbabde2018-11-22 15:33:30 +0530171 for content in contents:
scmmishra11925292018-11-20 17:38:01 +0530172 if content.doctype in ('Article', 'Video'):
173 status = check_content_completion(content.name, content.doctype, course_enrollment.name)
174 progress.append({'content': content.name, 'content_type': content.doctype, 'is_complete': status})
175 elif content.doctype == 'Quiz':
scmmishraa592f702018-11-20 18:37:01 +0530176 status, score, result = check_quiz_completion(content, course_enrollment.name)
177 progress.append({'content': content.name, 'content_type': content.doctype, 'is_complete': status, 'score': score, 'result': result})
scmmishra11925292018-11-20 17:38:01 +0530178 return progress
scmmishra35bf5612018-11-13 16:36:22 +0530179
scmmishra11925292018-11-20 17:38:01 +0530180def check_content_completion(content_name, content_type, enrollment_name):
181 activity = frappe.get_list("Course Activity", filters={'enrollment': enrollment_name, 'content_type': content_type, 'content': content_name})
182 if activity:
183 return True
184 else:
185 return False
186
187def check_quiz_completion(quiz, enrollment_name):
188 attempts = frappe.get_list("Quiz Activity", filters={'enrollment': enrollment_name, 'quiz': quiz.name}, fields=["name", "activity_date", "score", "status"])
scmmishraa592f702018-11-20 18:37:01 +0530189 status = bool(len(attempts) == quiz.max_attempts)
190 score = None
191 result = None
scmmishrafdbabde2018-11-22 15:33:30 +0530192 if attempts:
193 if quiz.grading_basis == 'Last Highest Score':
194 attempts = sorted(attempts, key = lambda i: int(i.score), reverse=True)
195 score = attempts[0]['score']
196 result = attempts[0]['status']
197 if result == 'Pass':
198 status = True
scmmishraa592f702018-11-20 18:37:01 +0530199 return status, score, result
scmmishrafdbabde2018-11-22 15:33:30 +0530200
201@frappe.whitelist()
scmmishra97c994f2018-11-26 14:41:15 +0530202def get_course_meta(course_name, program_name):
scmmishrafdbabde2018-11-22 15:33:30 +0530203 course_enrollment = utils.get_course_enrollment(course_name)
scmmishra97c994f2018-11-26 14:41:15 +0530204 if not course_enrollment:
205 utils.enroll_in_course(course_name, program_name)
scmmishraa592f702018-11-20 18:37:01 +0530206 progress = get_course_progress(course_enrollment)
scmmishraa592f702018-11-20 18:37:01 +0530207 count = sum([act['is_complete'] for act in progress])
scmmishraa592f702018-11-20 18:37:01 +0530208 if count == 0:
209 return {'flag':'Start Course', 'content_type': progress[0]['content_type'], 'content': progress[0]['content']}
210 elif count == len(progress):
scmmishra798f13b2018-12-05 10:35:48 +0530211 return {'flag':'Completed', 'content_type': progress[0]['content_type'], 'content': progress[0]['content']}
scmmishraa592f702018-11-20 18:37:01 +0530212 elif count < len(progress):
213 next_item = next(item for item in progress if item['is_complete']==False)
214 return {'flag':'Continue', 'content_type': next_item['content_type'], 'content': next_item['content']}
scmmishraaffbfe72018-11-26 11:59:25 +0530215
216@frappe.whitelist()
scmmishra201fec32018-11-26 16:52:45 +0530217def get_program_progress(program_name):
scmmishra29558512018-11-26 19:16:54 +0530218 import math
scmmishraaffbfe72018-11-26 11:59:25 +0530219 program = frappe.get_doc("Program", program_name)
scmmishra97c994f2018-11-26 14:41:15 +0530220 program_enrollment = frappe.get_list("Program Enrollment", filters={'student': utils.get_current_student(), 'program': program_name })[0].name
scmmishra201fec32018-11-26 16:52:45 +0530221 program_meta = {}
scmmishra97c994f2018-11-26 14:41:15 +0530222 if not program_enrollment:
223 return None
224 else:
scmmishra201fec32018-11-26 16:52:45 +0530225 progress = []
scmmishra97c994f2018-11-26 14:41:15 +0530226 for course in program.get_all_children():
scmmishra201fec32018-11-26 16:52:45 +0530227 meta = get_course_meta(course.course, program_name)
228 is_complete = False
229 if meta['flag'] == "Complete":
230 is_complete = True
231 progress.append({'course_name': course.course_name, 'name': course.course, 'is_complete': is_complete})
232 program_meta['progress'] = progress
233 program_meta['name'] = program_name
234 program_meta['program'] = program.program_name
scmmishra29558512018-11-26 19:16:54 +0530235 program_meta['percentage'] = math.ceil((sum([item['is_complete'] for item in progress] * 100)/len(progress)))
236 return program_meta
237
238@frappe.whitelist()
239def get_joining_date():
240 student = frappe.get_doc("Student", utils.get_current_student())
241 return student.joining_date