diff --git a/community/hooks.py b/community/hooks.py index 6d04ed83..eca23eac 100644 --- a/community/hooks.py +++ b/community/hooks.py @@ -161,10 +161,10 @@ whitelist = [ "/socket.io", "/hackathons", "/dashboard", - "/join-request" + "/join-request", "/add-a-new-batch", "/new-sign-up", - "/message" + "/message", "/about" ] whitelist_rules = [{"from_route": p, "to_route": p[1:]} for p in whitelist] @@ -188,4 +188,8 @@ update_website_context = 'community.widgets.update_website_context' # community_lesson_page_extension = None ## Markdown Macros for Lessons -# community_markdown_macro_renderers = {"Exercise": "myapp.mymodule.plugins.render_exercise"} +community_markdown_macro_renderers = { + "Exercise": "community.plugins.exercise_renderer", + "Quiz": "community.plugins.quiz_renderer", + "YouTubeVideo": "community.plugins.youtube_video_renderer", +} diff --git a/community/lms/doctype/lesson/lesson.json b/community/lms/doctype/lesson/lesson.json index 18c20147..582596de 100644 --- a/community/lms/doctype/lesson/lesson.json +++ b/community/lms/doctype/lesson/lesson.json @@ -7,7 +7,6 @@ "engine": "InnoDB", "field_order": [ "chapter", - "lesson_type", "include_in_preview", "column_break_4", "title", @@ -24,14 +23,6 @@ "label": "Chapter", "options": "Chapter" }, - { - "default": "Video", - "fieldname": "lesson_type", - "fieldtype": "Select", - "in_list_view": 1, - "label": "Lesson Type", - "options": "Video\nText\nQuiz" - }, { "fieldname": "title", "fieldtype": "Data", @@ -42,6 +33,7 @@ "default": "1", "fieldname": "index_", "fieldtype": "Int", + "in_list_view": 1, "label": "Index" }, { @@ -52,7 +44,6 @@ { "fieldname": "index_label", "fieldtype": "Data", - "in_list_view": 1, "label": "Index Label", "read_only": 1 }, @@ -73,7 +64,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-06-11 19:03:23.138165", + "modified": "2021-06-23 17:59:52.946515", "modified_by": "Administrator", "module": "LMS", "name": "Lesson", diff --git a/community/lms/doctype/lesson/lesson.py b/community/lms/doctype/lesson/lesson.py index 53fe5d89..d90fa2c1 100644 --- a/community/lms/doctype/lesson/lesson.py +++ b/community/lms/doctype/lesson/lesson.py @@ -9,27 +9,35 @@ from ...md import markdown_to_html, find_macros class Lesson(Document): def before_save(self): - macros = find_macros(self.body) - exercises = [value for name, value in macros if name == "Exercise"] + dynamic_documents = ["Exercise", "Quiz"] + for section in dynamic_documents: + self.update_lesson_name_in_document(section) + def update_lesson_name_in_document(self, section): + doctype_map= { + "Exercise": "Exercise", + "Quiz": "LMS Quiz" + } + macros = find_macros(self.body) + documents = [value for name, value in macros if name == section] index = 1 - for name in exercises: - e = frappe.get_doc("Exercise", name) + for name in documents: + e = frappe.get_doc(doctype_map[section], name) e.lesson = self.name e.index_ = index e.save() index += 1 - self.update_orphan_exercises(exercises) + self.update_orphan_documents(doctype_map[section], documents) - def update_orphan_exercises(self, active_exercises): - """Updates the exercises that were previously part of this lesson, + def update_orphan_documents(self, doctype, documents): + """Updates the documents that were previously part of this lesson, but not any more. """ - linked_exercises = {row['name'] for row in frappe.get_all('Exercise', {"lesson": self.name})} - active_exercises = set(active_exercises) - orphan_exercises = linked_exercises - active_exercises - for name in orphan_exercises: - ex = frappe.get_doc("Exercise", name) + linked_documents = {row['name'] for row in frappe.get_all(doctype, {"lesson": self.name})} + active_documents = set(documents) + orphan_documents = linked_documents - active_documents + for name in orphan_documents: + ex = frappe.get_doc(doctype, name) ex.lesson = None ex.index_ = 0 ex.index_label = "" @@ -92,13 +100,30 @@ def update_progress(lesson): course_progress.save(ignore_permissions=True) def all_dynamic_content_submitted(lesson, user): + all_exercises_submitted = check_all_exercise_submission(lesson, user) + all_quiz_submitted = check_all_quiz_submitted(lesson, user) + return all_exercises_submitted and all_quiz_submitted + +def check_all_exercise_submission(lesson, user): exercise_names = frappe.get_list("Exercise", {"lesson": lesson}, pluck="name", ignore_permissions=True) - all_exercises_submitted = False + if not len(exercise_names): + return True query = { "exercise": ["in", exercise_names], "owner": user } if frappe.db.count("Exercise Submission", query) == len(exercise_names): - all_exercises_submitted = True + return True + return False - return all_exercises_submitted +def check_all_quiz_submitted(lesson, user): + quizzes = frappe.get_list("LMS Quiz", {"lesson": lesson}, pluck="name", ignore_permissions=True) + if not len(quizzes): + return True + query = { + "quiz": ["in", quizzes], + "owner": user + } + if frappe.db.count("LMS Quiz Submission", query) == len(quizzes): + return True + return False diff --git a/community/lms/doctype/lms_course/lms_course.py b/community/lms/doctype/lms_course/lms_course.py index 7f77cd5d..a98a5e63 100644 --- a/community/lms/doctype/lms_course/lms_course.py +++ b/community/lms/doctype/lms_course/lms_course.py @@ -199,13 +199,15 @@ class LMSCourse(Document): } if batch: filters["batch"] = batch - return frappe.db.get_value("LMS Batch Membership", filters, ["name","batch", "current_lesson"], as_dict=True) + membership = frappe.db.get_value("LMS Batch Membership", filters, ["name","batch", "current_lesson"], as_dict=True) + if membership and membership.batch: + membership.batch_title = frappe.db.get_value("LMS Batch", membership.batch, "title") + return membership - def get_all_memberships(self, member=frappe.session.user): + def get_all_memberships(self, member): all_memberships = frappe.get_all("LMS Batch Membership", {"member": member, "course": self.name}, ["batch"]) for membership in all_memberships: membership.batch_title = frappe.db.get_value("LMS Batch", membership.batch, "title") - print(all_memberships) return all_memberships def get_mentors(self, batch=None): diff --git a/community/lms/doctype/lms_option/__init__.py b/community/lms/doctype/lms_option/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/community/lms/doctype/lms_option/lms_option.json b/community/lms/doctype/lms_option/lms_option.json new file mode 100644 index 00000000..eee21142 --- /dev/null +++ b/community/lms/doctype/lms_option/lms_option.json @@ -0,0 +1,36 @@ +{ + "actions": [], + "creation": "2021-06-07 10:46:10.402684", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "option", + "is_correct" + ], + "fields": [ + { + "fieldname": "option", + "fieldtype": "Data", + "label": "Option" + }, + { + "default": "0", + "fieldname": "is_correct", + "fieldtype": "Check", + "label": "Is Correct" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-06-07 10:48:45.290227", + "modified_by": "Administrator", + "module": "LMS", + "name": "LMS Option", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/community/lms/doctype/lms_option/lms_option.py b/community/lms/doctype/lms_option/lms_option.py new file mode 100644 index 00000000..d64d78b5 --- /dev/null +++ b/community/lms/doctype/lms_option/lms_option.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, FOSS United and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class LMSOption(Document): + pass diff --git a/community/lms/doctype/lms_quiz/__init__.py b/community/lms/doctype/lms_quiz/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/community/lms/doctype/lms_quiz/lms_quiz.js b/community/lms/doctype/lms_quiz/lms_quiz.js new file mode 100644 index 00000000..5f8fa5d3 --- /dev/null +++ b/community/lms/doctype/lms_quiz/lms_quiz.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, FOSS United and contributors +// For license information, please see license.txt + +frappe.ui.form.on('LMS Quiz', { + // refresh: function(frm) { + + // } +}); diff --git a/community/lms/doctype/lms_quiz/lms_quiz.json b/community/lms/doctype/lms_quiz/lms_quiz.json new file mode 100644 index 00000000..85cd2c42 --- /dev/null +++ b/community/lms/doctype/lms_quiz/lms_quiz.json @@ -0,0 +1,57 @@ +{ + "actions": [], + "autoname": "field:title", + "creation": "2021-06-07 10:50:17.893625", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "questions", + "lesson" + ], + "fields": [ + { + "fieldname": "title", + "fieldtype": "Data", + "label": "Title", + "unique": 1 + }, + { + "fieldname": "questions", + "fieldtype": "Table", + "label": "Questions", + "options": "LMS Quiz Question" + }, + { + "fieldname": "lesson", + "fieldtype": "Link", + "label": "Lesson", + "options": "Lesson" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-06-23 17:58:57.642873", + "modified_by": "Administrator", + "module": "LMS", + "name": "LMS Quiz", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/community/lms/doctype/lms_quiz/lms_quiz.py b/community/lms/doctype/lms_quiz/lms_quiz.py new file mode 100644 index 00000000..de002046 --- /dev/null +++ b/community/lms/doctype/lms_quiz/lms_quiz.py @@ -0,0 +1,79 @@ +# Copyright (c) 2021, FOSS United and contributors +# For license information, please see license.txt + +from community.lms.doctype.lesson.lesson import update_progress +import frappe +from frappe.model.document import Document +import json +from frappe import _ +from ..lesson.lesson import update_progress + +class LMSQuiz(Document): + def validate(self): + self.validate_correct_answers() + + def validate_correct_answers(self): + for question in self.questions: + correct_options = self.get_correct_options(question) + + if len(correct_options) > 1: + question.multiple = 1 + + if not len(correct_options): + frappe.throw(_("At least one answer must be correct for this question: {0}").format(frappe.bold(question.question))) + + def get_correct_options(self, question): + correct_option_fields = ["is_correct_1", "is_correct_2", "is_correct_3", "is_correct_4"] + return list(filter(lambda x: question.get(x) == 1, correct_option_fields)) + + def get_last_submission_details(self): + """Returns the latest submission for this user. + """ + user = frappe.session.user + if not user or user == "Guest": + return + + result = frappe.get_all('LMS Quiz Submission', + fields="*", + filters={ + "owner": user, + "quiz": self.name + }, + order_by="creation desc", + page_length=1) + + if result: + return result[0] + +@frappe.whitelist() +def submit(quiz, result): + score = 0 + answer_map = { + "is_correct_1": "option_1", + "is_correct_2": "option_2", + "is_correct_3": "option_3", + "is_correct_4": "option_4" + } + result = json.loads(result) + quiz_details = frappe.get_doc("LMS Quiz", quiz) + + for response in result: + match = list(filter(lambda x: x.question == response.get("question"), quiz_details.questions))[0] + correct_options = quiz_details.get_correct_options(match) + correct_answers = [ match.get(answer_map[option]) for option in correct_options ] + + if response.get("answer") == correct_answers: + response["result"] = "Right" + score += 1 + else: + response["result"] = "Wrong" + response["answer"] = ("").join([ ans if idx == len(response.get("answer")) -1 else ans + ", " for idx, ans in enumerate(response.get("answer")) ]) + + frappe.get_doc({ + "doctype": "LMS Quiz Submission", + "quiz": quiz, + "result": result, + "score": score + }).save(ignore_permissions=True) + update_progress(quiz_details.lesson) + return score diff --git a/community/lms/doctype/lms_quiz/test_lms_quiz.py b/community/lms/doctype/lms_quiz/test_lms_quiz.py new file mode 100644 index 00000000..e2df8fda --- /dev/null +++ b/community/lms/doctype/lms_quiz/test_lms_quiz.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, FOSS United and Contributors +# See license.txt + +# import frappe +import unittest + +class TestLMSQuiz(unittest.TestCase): + pass diff --git a/community/lms/doctype/lms_quiz_question/__init__.py b/community/lms/doctype/lms_quiz_question/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/community/lms/doctype/lms_quiz_question/lms_quiz_question.json b/community/lms/doctype/lms_quiz_question/lms_quiz_question.json new file mode 100644 index 00000000..b42c6bad --- /dev/null +++ b/community/lms/doctype/lms_quiz_question/lms_quiz_question.json @@ -0,0 +1,118 @@ +{ + "actions": [], + "creation": "2021-06-07 10:48:57.994714", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "question", + "options_section", + "option_1", + "is_correct_1", + "section_break_5", + "option_2", + "is_correct_2", + "column_break_4", + "option_3", + "is_correct_3", + "section_break_11", + "option_4", + "is_correct_4", + "multiple" + ], + "fields": [ + { + "fieldname": "question", + "fieldtype": "Text", + "in_list_view": 1, + "label": "Question", + "reqd": 1 + }, + { + "fieldname": "option_1", + "fieldtype": "Data", + "label": "Option 1", + "reqd": 1 + }, + { + "fieldname": "option_2", + "fieldtype": "Data", + "label": "Option 2", + "reqd": 1 + }, + { + "fieldname": "option_3", + "fieldtype": "Data", + "label": "Option 3" + }, + { + "fieldname": "option_4", + "fieldtype": "Data", + "label": "Option 4" + }, + { + "default": "0", + "depends_on": "option_1", + "fieldname": "is_correct_1", + "fieldtype": "Check", + "label": "Is Correct" + }, + { + "default": "0", + "depends_on": "option_2", + "fieldname": "is_correct_2", + "fieldtype": "Check", + "label": "Is Correct" + }, + { + "default": "0", + "depends_on": "option_3", + "fieldname": "is_correct_3", + "fieldtype": "Check", + "label": "Is Correct" + }, + { + "default": "0", + "depends_on": "option_4", + "fieldname": "is_correct_4", + "fieldtype": "Check", + "label": "Is Correct" + }, + { + "default": "0", + "fieldname": "multiple", + "fieldtype": "Check", + "hidden": 1, + "label": "Multiple Correct Answers", + "read_only": 1 + }, + { + "fieldname": "options_section", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Section Break" + }, + { + "fieldname": "section_break_5", + "fieldtype": "Section Break" + }, + { + "fieldname": "section_break_11", + "fieldtype": "Section Break" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-06-22 16:54:13.133859", + "modified_by": "Administrator", + "module": "LMS", + "name": "LMS Quiz Question", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/community/lms/doctype/lms_quiz_question/lms_quiz_question.py b/community/lms/doctype/lms_quiz_question/lms_quiz_question.py new file mode 100644 index 00000000..0435197b --- /dev/null +++ b/community/lms/doctype/lms_quiz_question/lms_quiz_question.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, FOSS United and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class LMSQuizQuestion(Document): + pass diff --git a/community/lms/doctype/lms_quiz_result/__init__.py b/community/lms/doctype/lms_quiz_result/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/community/lms/doctype/lms_quiz_result/lms_quiz_result.json b/community/lms/doctype/lms_quiz_result/lms_quiz_result.json new file mode 100644 index 00000000..255b8a9a --- /dev/null +++ b/community/lms/doctype/lms_quiz_result/lms_quiz_result.json @@ -0,0 +1,45 @@ +{ + "actions": [], + "creation": "2021-06-07 14:19:23.683323", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "question", + "answer", + "result" + ], + "fields": [ + { + "fieldname": "question", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Question" + }, + { + "fieldname": "result", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Result", + "options": "Right\nWrong" + }, + { + "fieldname": "answer", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Users Response" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-06-22 18:32:28.813159", + "modified_by": "Administrator", + "module": "LMS", + "name": "LMS Quiz Result", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/community/lms/doctype/lms_quiz_result/lms_quiz_result.py b/community/lms/doctype/lms_quiz_result/lms_quiz_result.py new file mode 100644 index 00000000..cfe9a9de --- /dev/null +++ b/community/lms/doctype/lms_quiz_result/lms_quiz_result.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, FOSS United and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class LMSQuizResult(Document): + pass diff --git a/community/lms/doctype/lms_quiz_submission/__init__.py b/community/lms/doctype/lms_quiz_submission/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/community/lms/doctype/lms_quiz_submission/lms_quiz_submission.js b/community/lms/doctype/lms_quiz_submission/lms_quiz_submission.js new file mode 100644 index 00000000..b4b0c176 --- /dev/null +++ b/community/lms/doctype/lms_quiz_submission/lms_quiz_submission.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, FOSS United and contributors +// For license information, please see license.txt + +frappe.ui.form.on('LMS Quiz Submission', { + // refresh: function(frm) { + + // } +}); diff --git a/community/lms/doctype/lms_quiz_submission/lms_quiz_submission.json b/community/lms/doctype/lms_quiz_submission/lms_quiz_submission.json new file mode 100644 index 00000000..63cd97ef --- /dev/null +++ b/community/lms/doctype/lms_quiz_submission/lms_quiz_submission.json @@ -0,0 +1,55 @@ +{ + "actions": [], + "creation": "2021-06-07 14:19:54.958989", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "quiz", + "result", + "score" + ], + "fields": [ + { + "fieldname": "quiz", + "fieldtype": "Link", + "label": "Quiz", + "options": "LMS Quiz" + }, + { + "fieldname": "result", + "fieldtype": "Table", + "label": "Result", + "options": "LMS Quiz Result" + }, + { + "fieldname": "score", + "fieldtype": "Data", + "label": "Score" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-06-07 14:19:54.958989", + "modified_by": "Administrator", + "module": "LMS", + "name": "LMS Quiz Submission", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/community/lms/doctype/lms_quiz_submission/lms_quiz_submission.py b/community/lms/doctype/lms_quiz_submission/lms_quiz_submission.py new file mode 100644 index 00000000..3d1e29c7 --- /dev/null +++ b/community/lms/doctype/lms_quiz_submission/lms_quiz_submission.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, FOSS United and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class LMSQuizSubmission(Document): + pass diff --git a/community/lms/doctype/lms_quiz_submission/test_lms_quiz_submission.py b/community/lms/doctype/lms_quiz_submission/test_lms_quiz_submission.py new file mode 100644 index 00000000..eddd8df4 --- /dev/null +++ b/community/lms/doctype/lms_quiz_submission/test_lms_quiz_submission.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, FOSS United and Contributors +# See license.txt + +# import frappe +import unittest + +class TestLMSQuizSubmission(unittest.TestCase): + pass diff --git a/community/lms/doctype/lms_section/lms_section.py b/community/lms/doctype/lms_section/lms_section.py new file mode 100644 index 00000000..a40f86c5 --- /dev/null +++ b/community/lms/doctype/lms_section/lms_section.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, FOSS United and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class LMSSection(Document): + def __repr__(self): + return f"" + + def get_exercise(self): + if self.type == "exercise": + return frappe.get_doc("Exercise", self.id) + + def get_quiz(self): + if self.type == "quiz": + return frappe.get_doc("LMS Quiz", self.id) + + def get_latest_code_for_user(self): + """Returns the latest code for the logged in user. + """ + if not frappe.session.user or frappe.session.user == "Guest": + return self.contents + result = frappe.get_all('Code Revision', + fields=["code"], + filters={ + "author": frappe.session.user, + "section": self.name + }, + order_by="creation desc", + page_length=1) + if result: + return result[0]['code'] + else: + return self.contents diff --git a/community/lms/widgets/BatchTabs.html b/community/lms/widgets/BatchTabs.html index 1b6d61d9..07c4c71a 100644 --- a/community/lms/widgets/BatchTabs.html +++ b/community/lms/widgets/BatchTabs.html @@ -2,16 +2,17 @@ Courses /{% if course.is_mentor(frappe.session.user) %} {{ course.title }} {% else %} {{ course.title }} {% endif %} - {% set all_memberships = course.get_all_memberships() %} - {% if all_memberships | length > 1 %} - @@ -28,7 +29,8 @@ Home diff --git a/community/lms/widgets/ChapterTeaser.html b/community/lms/widgets/ChapterTeaser.html index b4a0179f..5a49f5b3 100644 --- a/community/lms/widgets/ChapterTeaser.html +++ b/community/lms/widgets/ChapterTeaser.html @@ -7,9 +7,17 @@
{% for lesson in chapter.get_lessons() %}
- {{ lesson.title }} + {% if show_link or lesson.include_in_preview %} + {{ lesson.title }} + {% else %} +
+ + {{ lesson.title }} + + +
+ {% endif %} {% if show_progress and not course.is_mentor(frappe.session.user) and lesson.get_progress() %} {{ lesson.get_progress() }} {% endif %} @@ -18,36 +26,3 @@
- - diff --git a/community/plugins.py b/community/plugins.py index 58fb2765..76cfd4d0 100644 --- a/community/plugins.py +++ b/community/plugins.py @@ -14,6 +14,8 @@ The PageExtension is used to load additinal stylesheets and scripts to be loaded in a webpage. """ +import frappe + class PageExtension: """PageExtension is a plugin to inject custom styles and scripts into a web page. @@ -64,3 +66,24 @@ class ProfileTab: Every subclass must implement this. """ raise NotImplementedError() + +def quiz_renderer(quiz_name): + quiz = frappe.get_doc("LMS Quiz", quiz_name) + context = dict(quiz=quiz) + return frappe.render_template("templates/quiz.html", context) + +def exercise_renderer(argument): + exercise = frappe.get_doc("Exercise", argument) + context = dict(exercise=exercise) + return frappe.render_template("templates/exercise.html", context) + +def youtube_video_renderer(video_id): + return f""" + + """ diff --git a/community/public/css/style.css b/community/public/css/style.css index 30d07de8..a3116517 100644 --- a/community/public/css/style.css +++ b/community/public/css/style.css @@ -23,6 +23,7 @@ --cta-color: var(--c4); --send-message: var(--c7); --received-message: var(--c8); + --checkbox-size: 14px; --control-bg: var(--gray-100); } @@ -228,6 +229,10 @@ section { margin-top: 30px; } +input[type=checkbox] { + appearance: auto; +} + .partiallycomplete { background: #FEF4E2; color: #976417; diff --git a/community/public/css/style.less b/community/public/css/style.less index b74cd666..3de921c9 100644 --- a/community/public/css/style.less +++ b/community/public/css/style.less @@ -76,11 +76,6 @@ h2 { text-decoration: none } -.no-preview:hover { - cursor: pointer; - color: #2490ef; -} - section { padding: 60px 0px; } diff --git a/community/templates/exercise.html b/community/templates/exercise.html new file mode 100644 index 00000000..fca923b7 --- /dev/null +++ b/community/templates/exercise.html @@ -0,0 +1,10 @@ +
+

Exercise {{exercise.index_label}}: {{ exercise.title }}

+
{{frappe.utils.md_to_html(exercise.description)}}
+ + {% set submission = exercise.get_user_submission() %} + +
{{ submission.solution if submission else exercise.code }}
+
diff --git a/community/templates/quiz.html b/community/templates/quiz.html new file mode 100644 index 00000000..3e5de49e --- /dev/null +++ b/community/templates/quiz.html @@ -0,0 +1,30 @@ +{% set last_submission = quiz.get_last_submission_details() %} +{% if last_submission %} +
+
Last Submitted On: {{ frappe.utils.pretty_date(last_submission.creation) }}
+
Last Submission Score: {{ last_submission.score }}
+
+{% endif %} +

{{ quiz.title }}

+
+ {% for question in quiz.questions %} +
+

{{ loop.index }}. {{ question.question }}

+ {% set options = [question.option_1, question.option_2, question.option_3, question.option_4] %} + {% for option in options %} + {% if option %} +
+ + {{ option }} +
+ {% endif %} + {% endfor %} +
+ {% endfor %} + + +

+
+
diff --git a/community/www/batch/home.html b/community/www/batch/home.html index d53888d4..e7542071 100644 --- a/community/www/batch/home.html +++ b/community/www/batch/home.html @@ -11,7 +11,7 @@
{{ widgets.BatchTabs(course=course, membership=membership) }}
- {{ widgets.CourseOutline(course=course, batch=batch, show_link=True, show_progress=True) }} + {{ widgets.CourseOutline(course=course, batch=batch, show_link=membership, show_progress=True) }}
{% if batch %} diff --git a/community/www/batch/learn.html b/community/www/batch/learn.html index a65dc4e0..a9c08b02 100644 --- a/community/www/batch/learn.html +++ b/community/www/batch/learn.html @@ -36,13 +36,13 @@ Checkout Course Details.
{% endif %} - + {% if membership %} {{ pagination(prev_chap, prev_url, next_chap, next_url) }} + {% endif %} {% endblock %} - {% macro pagination(prev_chap, prev_url, next_chap, next_url) %}
{% if prev_url %} diff --git a/community/www/batch/learn.js b/community/www/batch/learn.js index e15c5c5c..69699ac4 100644 --- a/community/www/batch/learn.js +++ b/community/www/batch/learn.js @@ -1,4 +1,6 @@ frappe.ready(() => { + + /* Save Lesson Progress */ if ($(".title").attr("data-membership") && !$(".title").hasClass("is_mentor")) { frappe.call({ method: "community.lms.doctype.lesson.lesson.save_progress", @@ -8,10 +10,60 @@ frappe.ready(() => { } }) } + + /* Save Current Lesson */ if ($(".title").attr("data-membership")) { frappe.call("community.lms.api.save_current_lesson", { course_name: $(".title").attr("data-course"), lesson_name: $(".title").attr("data-lesson") }) } + + /* Submit Quiz */ + $("#submit-quiz").click((e) => { + e.preventDefault(); + console.log("click") + var result = []; + $('.question').each((i, element) => { + var options = $(element).find(".option"); + var answers = []; + options.filter((i, op) => $(op).prop("checked")).each((i, elem) => answers.push(decodeURIComponent(elem.value))); + result.push({ + "question": element.dataset.question, + "answer": answers + }); + }); + frappe.call({ + method: "community.lms.doctype.lms_quiz.lms_quiz.submit", + args: { + quiz: $("#title").text(), + result: result + }, + callback: (data) => { + $("#submit-quiz").addClass("hide"); + $("#try-again").removeClass("hide"); + $(":input[type='checkbox']").prop("disabled", true); + $(":input[type='radio']").prop("disabled", true); + if (data.message == result.length) { + $(".success-message").text("Congratulations, you cleared the quiz!"); + } + else { + $(".success-message").text("Some of your answers weren't correct. You can give it another shot."); + } + $(".score").text(`Score: ${data.message}/${result.length}`); + } + }) + }) + + /* Try the quiz again */ + $("#try-again").click((e) => { + e.preventDefault(); + $(":input[type='checkbox']").prop("disabled", false); + $(":input[type='radio']").prop("disabled", false); + $("#quiz-form").trigger("reset"); + $(".success-message").text(""); + $(".score").text(""); + $("#submit-quiz").removeClass("hide"); + $("#try-again").addClass("hide"); + }) }) diff --git a/community/www/batch/utils.py b/community/www/batch/utils.py index 23060680..ea8845c7 100644 --- a/community/www/batch/utils.py +++ b/community/www/batch/utils.py @@ -27,7 +27,7 @@ def get_common_context(context): context.members = course.get_mentors(membership.batch) + course.get_students(membership.batch) context.member_count = len(context.members) - context.course.query_parameter = "?batch=" + membership.batch if membership and membership.batch else "" + context.course.query_parameter = "?batch=" + membership.batch if membership and membership.batch else " " context.livecode_url = get_livecode_url() def get_livecode_url(): diff --git a/community/www/courses/course.html b/community/www/courses/course.html index 7de4c538..7947fd5f 100644 --- a/community/www/courses/course.html +++ b/community/www/courses/course.html @@ -4,6 +4,7 @@ {% block head_include %} + {% endblock %} {% block content %} @@ -30,7 +31,7 @@ {{ CourseDescription(course) }} {{ widgets.InstructorSection(instructor=course.get_instructor()) }} {{ BatchSection(course) }} - {{ widgets.CourseOutline(course=course, show_link=False) }} + {{ widgets.CourseOutline(course=course, show_link=membership) }}
diff --git a/community/www/courses/course.py b/community/www/courses/course.py index eda11084..7f5c5fab 100644 --- a/community/www/courses/course.py +++ b/community/www/courses/course.py @@ -16,9 +16,9 @@ def get_context(context): raise frappe.Redirect context.course = course - context.course.query_parameter = "" - if not course.is_mentor(frappe.session.user): - batch = course.get_membership(frappe.session.user) - if batch: - frappe.local.flags.redirect_location = f"/courses/{course.name}/learn" - raise frappe.Redirect + membership = course.get_membership(frappe.session.user) + context.course.query_parameter = "?batch=" + membership.batch if membership and membership.batch else "" + context.membership = membership + if not course.is_mentor(frappe.session.user) and membership: + frappe.local.flags.redirect_location = f"/courses/{course.name}/learn" + raise frappe.Redirect