Compare commits
16 Commits
mon-fixes-
...
page-conte
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9caf44cdbd | ||
|
|
67708325ae | ||
|
|
3e99577401 | ||
|
|
621d01d502 | ||
|
|
aa20136223 | ||
|
|
5a7afb3092 | ||
|
|
f8948ac2ef | ||
|
|
8b1576a028 | ||
|
|
56d8a72a7d | ||
|
|
f6c11ce52f | ||
|
|
0284c9305c | ||
|
|
d785fb7562 | ||
|
|
9f50af4ebd | ||
|
|
4c3645f0d4 | ||
|
|
5d96bf544d | ||
|
|
1cb81de5c0 |
@@ -161,10 +161,10 @@ whitelist = [
|
|||||||
"/socket.io",
|
"/socket.io",
|
||||||
"/hackathons",
|
"/hackathons",
|
||||||
"/dashboard",
|
"/dashboard",
|
||||||
"/join-request"
|
"/join-request",
|
||||||
"/add-a-new-batch",
|
"/add-a-new-batch",
|
||||||
"/new-sign-up",
|
"/new-sign-up",
|
||||||
"/message"
|
"/message",
|
||||||
"/about"
|
"/about"
|
||||||
]
|
]
|
||||||
whitelist_rules = [{"from_route": p, "to_route": p[1:]} for p in whitelist]
|
whitelist_rules = [{"from_route": p, "to_route": p[1:]} for p in whitelist]
|
||||||
@@ -187,5 +187,13 @@ update_website_context = 'community.widgets.update_website_context'
|
|||||||
## subclass of community.community.plugins.PageExtension
|
## subclass of community.community.plugins.PageExtension
|
||||||
# community_lesson_page_extension = None
|
# community_lesson_page_extension = None
|
||||||
|
|
||||||
|
community_lesson_page_extensions = [
|
||||||
|
"community.plugins.LiveCodeExtension"
|
||||||
|
]
|
||||||
|
|
||||||
## Markdown Macros for Lessons
|
## 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",
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,12 +6,17 @@
|
|||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"exercise",
|
"exercise",
|
||||||
"solution",
|
"status",
|
||||||
|
"batch",
|
||||||
|
"column_break_4",
|
||||||
"exercise_title",
|
"exercise_title",
|
||||||
"course",
|
"course",
|
||||||
"batch",
|
|
||||||
"lesson",
|
"lesson",
|
||||||
"image"
|
"section_break_8",
|
||||||
|
"solution",
|
||||||
|
"image",
|
||||||
|
"test_results",
|
||||||
|
"comments"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -21,12 +26,6 @@
|
|||||||
"label": "Exercise",
|
"label": "Exercise",
|
||||||
"options": "Exercise"
|
"options": "Exercise"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "solution",
|
|
||||||
"fieldtype": "Code",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Solution"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fetch_from": "exercise.title",
|
"fetch_from": "exercise.title",
|
||||||
"fieldname": "exercise_title",
|
"fieldname": "exercise_title",
|
||||||
@@ -61,11 +60,41 @@
|
|||||||
"fieldtype": "Code",
|
"fieldtype": "Code",
|
||||||
"label": "Image",
|
"label": "Image",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "status",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Status",
|
||||||
|
"options": "Correct\nIncorrect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "test_results",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Test Results"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "comments",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Comments"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "solution",
|
||||||
|
"fieldtype": "Code",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Solution"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_4",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_8",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-05-21 11:28:45.833018",
|
"modified": "2021-06-24 16:22:50.570845",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "Exercise Submission",
|
"name": "Exercise Submission",
|
||||||
|
|||||||
@@ -2,7 +2,47 @@
|
|||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.ui.form.on('Lesson', {
|
frappe.ui.form.on('Lesson', {
|
||||||
// refresh: function(frm) {
|
setup: function (frm) {
|
||||||
|
frm.trigger('setup_help');
|
||||||
|
},
|
||||||
|
setup_help(frm) {
|
||||||
|
frm.get_field('help').html(`
|
||||||
|
<p>You can add some more additional content to the lesson using a special syntax. The table below mentions all types of dynamic content that you can add to the lessons and the syntax for the same.</p>
|
||||||
|
<div class="row font-weight-bold mb-3">
|
||||||
|
<div class="col-sm-4">
|
||||||
|
Content Type
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
Syntax
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
// }
|
<div class="row mb-3">
|
||||||
|
<div class="col-sm-4">
|
||||||
|
YouTube Video
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
{{ YouTubeVideo("unique_embed_id") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-sm-4">
|
||||||
|
Exercise
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
{{ Exercise("exercise_name") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-sm-4">
|
||||||
|
Quiz
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
{{ Quiz("lms_quiz_name") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,14 +7,15 @@
|
|||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"chapter",
|
"chapter",
|
||||||
"lesson_type",
|
|
||||||
"include_in_preview",
|
"include_in_preview",
|
||||||
"column_break_4",
|
"column_break_4",
|
||||||
"title",
|
"title",
|
||||||
"index_",
|
"index_",
|
||||||
"index_label",
|
"index_label",
|
||||||
"section_break_6",
|
"section_break_6",
|
||||||
"body"
|
"body",
|
||||||
|
"help_section",
|
||||||
|
"help"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -24,14 +25,6 @@
|
|||||||
"label": "Chapter",
|
"label": "Chapter",
|
||||||
"options": "Chapter"
|
"options": "Chapter"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"default": "Video",
|
|
||||||
"fieldname": "lesson_type",
|
|
||||||
"fieldtype": "Select",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Lesson Type",
|
|
||||||
"options": "Video\nText\nQuiz"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "title",
|
"fieldname": "title",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
@@ -42,6 +35,7 @@
|
|||||||
"default": "1",
|
"default": "1",
|
||||||
"fieldname": "index_",
|
"fieldname": "index_",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
|
"in_list_view": 1,
|
||||||
"label": "Index"
|
"label": "Index"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -52,7 +46,6 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "index_label",
|
"fieldname": "index_label",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Index Label",
|
"label": "Index Label",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@@ -69,11 +62,20 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "column_break_4",
|
"fieldname": "column_break_4",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "help_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Help"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "help",
|
||||||
|
"fieldtype": "HTML"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-06-11 19:03:23.138165",
|
"modified": "2021-06-29 13:34:49.077363",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "Lesson",
|
"name": "Lesson",
|
||||||
|
|||||||
@@ -9,27 +9,35 @@ from ...md import markdown_to_html, find_macros
|
|||||||
|
|
||||||
class Lesson(Document):
|
class Lesson(Document):
|
||||||
def before_save(self):
|
def before_save(self):
|
||||||
macros = find_macros(self.body)
|
dynamic_documents = ["Exercise", "Quiz"]
|
||||||
exercises = [value for name, value in macros if name == "Exercise"]
|
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
|
index = 1
|
||||||
for name in exercises:
|
for name in documents:
|
||||||
e = frappe.get_doc("Exercise", name)
|
e = frappe.get_doc(doctype_map[section], name)
|
||||||
e.lesson = self.name
|
e.lesson = self.name
|
||||||
e.index_ = index
|
e.index_ = index
|
||||||
e.save()
|
e.save()
|
||||||
index += 1
|
index += 1
|
||||||
self.update_orphan_exercises(exercises)
|
self.update_orphan_documents(doctype_map[section], documents)
|
||||||
|
|
||||||
def update_orphan_exercises(self, active_exercises):
|
def update_orphan_documents(self, doctype, documents):
|
||||||
"""Updates the exercises that were previously part of this lesson,
|
"""Updates the documents that were previously part of this lesson,
|
||||||
but not any more.
|
but not any more.
|
||||||
"""
|
"""
|
||||||
linked_exercises = {row['name'] for row in frappe.get_all('Exercise', {"lesson": self.name})}
|
linked_documents = {row['name'] for row in frappe.get_all(doctype, {"lesson": self.name})}
|
||||||
active_exercises = set(active_exercises)
|
active_documents = set(documents)
|
||||||
orphan_exercises = linked_exercises - active_exercises
|
orphan_documents = linked_documents - active_documents
|
||||||
for name in orphan_exercises:
|
for name in orphan_documents:
|
||||||
ex = frappe.get_doc("Exercise", name)
|
ex = frappe.get_doc(doctype, name)
|
||||||
ex.lesson = None
|
ex.lesson = None
|
||||||
ex.index_ = 0
|
ex.index_ = 0
|
||||||
ex.index_label = ""
|
ex.index_label = ""
|
||||||
@@ -92,13 +100,30 @@ def update_progress(lesson):
|
|||||||
course_progress.save(ignore_permissions=True)
|
course_progress.save(ignore_permissions=True)
|
||||||
|
|
||||||
def all_dynamic_content_submitted(lesson, user):
|
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)
|
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 = {
|
query = {
|
||||||
"exercise": ["in", exercise_names],
|
"exercise": ["in", exercise_names],
|
||||||
"owner": user
|
"owner": user
|
||||||
}
|
}
|
||||||
if frappe.db.count("Exercise Submission", query) == len(exercise_names):
|
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
|
||||||
|
|||||||
@@ -199,13 +199,15 @@ class LMSCourse(Document):
|
|||||||
}
|
}
|
||||||
if batch:
|
if batch:
|
||||||
filters["batch"] = 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"])
|
all_memberships = frappe.get_all("LMS Batch Membership", {"member": member, "course": self.name}, ["batch"])
|
||||||
for membership in all_memberships:
|
for membership in all_memberships:
|
||||||
membership.batch_title = frappe.db.get_value("LMS Batch", membership.batch, "title")
|
membership.batch_title = frappe.db.get_value("LMS Batch", membership.batch, "title")
|
||||||
print(all_memberships)
|
|
||||||
return all_memberships
|
return all_memberships
|
||||||
|
|
||||||
def get_mentors(self, batch=None):
|
def get_mentors(self, batch=None):
|
||||||
|
|||||||
0
community/lms/doctype/lms_option/__init__.py
Normal file
0
community/lms/doctype/lms_option/__init__.py
Normal file
36
community/lms/doctype/lms_option/lms_option.json
Normal file
36
community/lms/doctype/lms_option/lms_option.json
Normal file
@@ -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
|
||||||
|
}
|
||||||
8
community/lms/doctype/lms_option/lms_option.py
Normal file
8
community/lms/doctype/lms_option/lms_option.py
Normal file
@@ -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
|
||||||
0
community/lms/doctype/lms_quiz/__init__.py
Normal file
0
community/lms/doctype/lms_quiz/__init__.py
Normal file
8
community/lms/doctype/lms_quiz/lms_quiz.js
Normal file
8
community/lms/doctype/lms_quiz/lms_quiz.js
Normal file
@@ -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) {
|
||||||
|
|
||||||
|
// }
|
||||||
|
});
|
||||||
57
community/lms/doctype/lms_quiz/lms_quiz.json
Normal file
57
community/lms/doctype/lms_quiz/lms_quiz.json
Normal file
@@ -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
|
||||||
|
}
|
||||||
79
community/lms/doctype/lms_quiz/lms_quiz.py
Normal file
79
community/lms/doctype/lms_quiz/lms_quiz.py
Normal file
@@ -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
|
||||||
8
community/lms/doctype/lms_quiz/test_lms_quiz.py
Normal file
8
community/lms/doctype/lms_quiz/test_lms_quiz.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Copyright (c) 2021, FOSS United and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
class TestLMSQuiz(unittest.TestCase):
|
||||||
|
pass
|
||||||
0
community/lms/doctype/lms_quiz_question/__init__.py
Normal file
0
community/lms/doctype/lms_quiz_question/__init__.py
Normal file
118
community/lms/doctype/lms_quiz_question/lms_quiz_question.json
Normal file
118
community/lms/doctype/lms_quiz_question/lms_quiz_question.json
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
0
community/lms/doctype/lms_quiz_result/__init__.py
Normal file
0
community/lms/doctype/lms_quiz_result/__init__.py
Normal file
45
community/lms/doctype/lms_quiz_result/lms_quiz_result.json
Normal file
45
community/lms/doctype/lms_quiz_result/lms_quiz_result.json
Normal file
@@ -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
|
||||||
|
}
|
||||||
8
community/lms/doctype/lms_quiz_result/lms_quiz_result.py
Normal file
8
community/lms/doctype/lms_quiz_result/lms_quiz_result.py
Normal file
@@ -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
|
||||||
@@ -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) {
|
||||||
|
|
||||||
|
// }
|
||||||
|
});
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Copyright (c) 2021, FOSS United and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
class TestLMSQuizSubmission(unittest.TestCase):
|
||||||
|
pass
|
||||||
37
community/lms/doctype/lms_section/lms_section.py
Normal file
37
community/lms/doctype/lms_section/lms_section.py
Normal file
@@ -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"<LMSSection {self.label!r}>"
|
||||||
|
|
||||||
|
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
|
||||||
@@ -2,16 +2,17 @@
|
|||||||
<a class="anchor_style" href="/courses">Courses</a> /{% if course.is_mentor(frappe.session.user) %} <a
|
<a class="anchor_style" href="/courses">Courses</a> /{% if course.is_mentor(frappe.session.user) %} <a
|
||||||
class="anchor_style" href="/courses/{{ course.name }}"> {{ course.title }}</a> {% else %} <span class="text-muted">
|
class="anchor_style" href="/courses/{{ course.name }}"> {{ course.title }}</a> {% else %} <span class="text-muted">
|
||||||
{{ course.title }}</span> {% endif %}
|
{{ course.title }}</span> {% endif %}
|
||||||
{% set all_memberships = course.get_all_memberships() %}
|
{% set all_memberships = course.get_all_memberships(frappe.session.user) %}
|
||||||
{% if all_memberships | length > 1 %}
|
{% if membership and membership.batch and all_memberships | length > 1 %}
|
||||||
<a class="nav-link pull-right" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true"
|
<a class="pull-right dropdown-item border rounded" style="width: 10rem;" href="#" id="navbarDropdown" role="button"
|
||||||
aria-expanded="false">
|
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
Switch Batch
|
{{ membership.batch_title }}
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||||
{% for data in all_memberships %}
|
{% for data in all_memberships %}
|
||||||
{% if data.batch != membership.batch %}
|
{% if data.batch != membership.batch %}
|
||||||
<a class="dropdown-item switch-batch" href="/courses/{{ course.name }}/home?batch={{ data.batch }}">{{ data.batch_title }}</a>
|
<a class="dropdown-item switch-batch"
|
||||||
|
href="/courses/{{ course.name }}/home?batch={{ data.batch }}">{{ data.batch_title }}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
@@ -28,7 +29,8 @@
|
|||||||
<a class="nav-link" id="home" href="/courses/{{course.name}}/home{{ course.query_parameter }}">Home</a>
|
<a class="nav-link" id="home" href="/courses/{{course.name}}/home{{ course.query_parameter }}">Home</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
{% set lesson_index = course.get_lesson_index(membership.current_lesson) if membership and membership.current_lesson else '1.1' %}
|
{% set lesson_index = course.get_lesson_index(membership.current_lesson) if membership and membership.current_lesson
|
||||||
|
else '1.1' %}
|
||||||
<a class="nav-link" id="learn"
|
<a class="nav-link" id="learn"
|
||||||
href="{{ course.get_learn_url(lesson_index) }}{{ course.query_parameter }}">Lessons</a>
|
href="{{ course.get_learn_url(lesson_index) }}{{ course.query_parameter }}">Lessons</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -7,9 +7,17 @@
|
|||||||
<div class="chapter-lessons">
|
<div class="chapter-lessons">
|
||||||
{% for lesson in chapter.get_lessons() %}
|
{% for lesson in chapter.get_lessons() %}
|
||||||
<div class="lesson-teaser">
|
<div class="lesson-teaser">
|
||||||
<a {% if show_link or lesson.include_in_preview %}
|
{% if show_link or lesson.include_in_preview %}
|
||||||
href="{{ course.get_learn_url(course.get_lesson_index(lesson.name)) }}{{course.query_parameter}}" {% else %} href="" class="no-preview"
|
<a class="" href="{{ course.get_learn_url(course.get_lesson_index(lesson.name)) }}{{course.query_parameter}}"
|
||||||
{% endif %} data-course="{{ course.name }}">{{ lesson.title }}</a>
|
data-course="{{ course.name }}">{{ lesson.title }}</a>
|
||||||
|
{% else %}
|
||||||
|
<div class="no-preview" title="This lesson is not available for preview">
|
||||||
|
<span style="color: #2490ef;">
|
||||||
|
{{ lesson.title }}
|
||||||
|
</span>
|
||||||
|
<i class="fa fa-lock ml-2"></i>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% if show_progress and not course.is_mentor(frappe.session.user) and lesson.get_progress() %}
|
{% if show_progress and not course.is_mentor(frappe.session.user) and lesson.get_progress() %}
|
||||||
<span class="ml-5 badge p-2 {{ lesson.get_slugified_class() }}"> {{ lesson.get_progress() }}</span>
|
<span class="ml-5 badge p-2 {{ lesson.get_slugified_class() }}"> {{ lesson.get_progress() }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -18,36 +26,3 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
|
||||||
frappe.ready(() => {
|
|
||||||
var d;
|
|
||||||
$(".no-preview").click((e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
e.stopImmediatePropagation();
|
|
||||||
var message = __("Please enroll for this course to access the lesson.");
|
|
||||||
var label = __("Checkout Upcoming Batches");
|
|
||||||
var action = "checkout_upcoming_batches";
|
|
||||||
d = frappe.msgprint({
|
|
||||||
title: __("This lesson is not available for preview!"),
|
|
||||||
message: message,
|
|
||||||
primary_action: {
|
|
||||||
"label": label,
|
|
||||||
"client_action": action,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
|
|
||||||
window.redirect_to_login = () => {
|
|
||||||
window.location.href = `/login?redirect-to=/courses/${$(".no-preview").attr("data-course")}`
|
|
||||||
}
|
|
||||||
|
|
||||||
window.checkout_upcoming_batches = () => {
|
|
||||||
if ($(".upcoming").length > 0) {
|
|
||||||
$('html,body').animate({ scrollTop: $(".upcoming").offset().top }, 300);
|
|
||||||
}
|
|
||||||
frappe.hide_msgprint();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|||||||
160
community/lms/workspace/lms/lms.json
Normal file
160
community/lms/workspace/lms/lms.json
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
{
|
||||||
|
"category": "Modules",
|
||||||
|
"charts": [],
|
||||||
|
"creation": "2021-06-29 13:05:28.741459",
|
||||||
|
"developer_mode_only": 0,
|
||||||
|
"disable_user_customization": 0,
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Workspace",
|
||||||
|
"extends_another_page": 0,
|
||||||
|
"hide_custom": 0,
|
||||||
|
"icon": "education",
|
||||||
|
"idx": 0,
|
||||||
|
"is_default": 0,
|
||||||
|
"is_standard": 1,
|
||||||
|
"label": "LMS",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "LMS",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Card Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "LMS Course",
|
||||||
|
"link_to": "LMS Course",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "LMS Batch",
|
||||||
|
"link_to": "LMS Batch",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "LMS Batch Membership",
|
||||||
|
"link_to": "LMS Batch Membership",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "LMS Course Mentor Mapping",
|
||||||
|
"link_to": "LMS Course Mentor Mapping",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Content",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Card Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Chapter",
|
||||||
|
"link_to": "Chapter",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Lesson",
|
||||||
|
"link_to": "Lesson",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Exercise",
|
||||||
|
"link_to": "Exercise",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Exercise Submission",
|
||||||
|
"link_to": "Exercise Submission",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "LMS Quiz",
|
||||||
|
"link_to": "LMS Quiz",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "LMS Quiz Submission",
|
||||||
|
"link_to": "LMS Quiz Submission",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"modified": "2021-06-29 15:11:07.324651",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "LMS",
|
||||||
|
"name": "LMS",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"pin_to_bottom": 0,
|
||||||
|
"pin_to_top": 1,
|
||||||
|
"shortcuts": [
|
||||||
|
{
|
||||||
|
"color": "#29CD42",
|
||||||
|
"doc_view": "List",
|
||||||
|
"format": "{} Published",
|
||||||
|
"label": "Courses",
|
||||||
|
"link_to": "LMS Course",
|
||||||
|
"stats_filter": "{\"is_published\": 1}",
|
||||||
|
"type": "DocType"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#29CD42",
|
||||||
|
"doc_view": "List",
|
||||||
|
"format": "{} Active ",
|
||||||
|
"label": "Batches",
|
||||||
|
"link_to": "LMS Batch",
|
||||||
|
"stats_filter": "{\"status\": \"Active\"}",
|
||||||
|
"type": "DocType"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#39E4A5",
|
||||||
|
"doc_view": "List",
|
||||||
|
"format": "{} Students",
|
||||||
|
"label": "Memberships",
|
||||||
|
"link_to": "LMS Batch Membership",
|
||||||
|
"stats_filter": "{\"member_type\": \"Student\"}",
|
||||||
|
"type": "DocType"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -14,6 +14,8 @@ The PageExtension is used to load additinal stylesheets and scripts to
|
|||||||
be loaded in a webpage.
|
be loaded in a webpage.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
|
||||||
class PageExtension:
|
class PageExtension:
|
||||||
"""PageExtension is a plugin to inject custom styles and scripts
|
"""PageExtension is a plugin to inject custom styles and scripts
|
||||||
into a web page.
|
into a web page.
|
||||||
@@ -64,3 +66,43 @@ class ProfileTab:
|
|||||||
Every subclass must implement this.
|
Every subclass must implement this.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
class LiveCodeExtension(PageExtension):
|
||||||
|
def render_header(self):
|
||||||
|
livecode_url = frappe.get_value("LMS Settings", None, "livecode_url")
|
||||||
|
context = {
|
||||||
|
"livecode_url": livecode_url
|
||||||
|
}
|
||||||
|
return frappe.render_template(
|
||||||
|
"templates/livecode/extension_header.html",
|
||||||
|
context)
|
||||||
|
|
||||||
|
def render_footer(self):
|
||||||
|
livecode_url = frappe.get_value("LMS Settings", None, "livecode_url")
|
||||||
|
context = {
|
||||||
|
"livecode_url": livecode_url
|
||||||
|
}
|
||||||
|
return frappe.render_template(
|
||||||
|
"templates/livecode/extension_footer.html",
|
||||||
|
context)
|
||||||
|
|
||||||
|
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"""
|
||||||
|
<iframe width="560" height="315"
|
||||||
|
src="https://www.youtube.com/embed/{video_id}"
|
||||||
|
title="YouTube video player"
|
||||||
|
frameborder="0"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||||
|
allowfullscreen>
|
||||||
|
</iframe>
|
||||||
|
"""
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
--cta-color: var(--c4);
|
--cta-color: var(--c4);
|
||||||
--send-message: var(--c7);
|
--send-message: var(--c7);
|
||||||
--received-message: var(--c8);
|
--received-message: var(--c8);
|
||||||
|
--checkbox-size: 14px;
|
||||||
--control-bg: var(--gray-100);
|
--control-bg: var(--gray-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,6 +229,10 @@ section {
|
|||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input[type=checkbox] {
|
||||||
|
appearance: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.partiallycomplete {
|
.partiallycomplete {
|
||||||
background: #FEF4E2;
|
background: #FEF4E2;
|
||||||
color: #976417;
|
color: #976417;
|
||||||
|
|||||||
@@ -76,11 +76,6 @@ h2 {
|
|||||||
text-decoration: none
|
text-decoration: none
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-preview:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
color: #2490ef;
|
|
||||||
}
|
|
||||||
|
|
||||||
section {
|
section {
|
||||||
padding: 60px 0px;
|
padding: 60px 0px;
|
||||||
}
|
}
|
||||||
|
|||||||
10
community/templates/exercise.html
Normal file
10
community/templates/exercise.html
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<div class="exercise">
|
||||||
|
<h3>Exercise {{exercise.index_label}}: {{ exercise.title }}</h3>
|
||||||
|
<div class="exercise-description">{{frappe.utils.md_to_html(exercise.description)}}</div>
|
||||||
|
|
||||||
|
{% set submission = exercise.get_user_submission() %}
|
||||||
|
|
||||||
|
<pre class="exercise" id="exercise-{{exercise.name}}"
|
||||||
|
data-last-submitted="{{ submission and submission.creation or '' }}" data-name="{{ exercise.name }}"
|
||||||
|
data-image='{{ (submission and submission.image or "") | tojson }}'><code class="language-joy">{{ submission.solution if submission else exercise.code }}</code></pre>
|
||||||
|
</div>
|
||||||
168
community/templates/livecode/extension_footer.html
Normal file
168
community/templates/livecode/extension_footer.html
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
<script type="text/javascript" src="/assets/frappe/node_modules/moment/min/moment-with-locales.min.js"></script>
|
||||||
|
<script type="text/javascript" src="/assets/frappe/node_modules/moment-timezone/builds/moment-timezone-with-data.min.js"></script>
|
||||||
|
<script type="text/javascript" src="/assets/frappe/js/frappe/utils/datetime.js"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
// comment_when is failing because of this
|
||||||
|
if (!frappe.sys_defaults) {
|
||||||
|
frappe.sys_defaults = {}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="{{ livecode_url }}/static/livecode.js"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="/assets/mon_school/js/livecode-files.js"></script>
|
||||||
|
|
||||||
|
<template id="livecode-template">
|
||||||
|
<div class="livecode-editor livecode-editor-inline">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-8 col-md-6">
|
||||||
|
<div class="controls">
|
||||||
|
<button class="run">Run</button>
|
||||||
|
|
||||||
|
<div class="exercise-controls pull-right">
|
||||||
|
<span style="padding-right: 10px;"><span class="last-submitted human-time" data-timestamp=""></span></span>
|
||||||
|
<button class="submit btn-primary">Submit</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="code-editor">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-8 col-md-6">
|
||||||
|
<div class="code-wrapper">
|
||||||
|
<textarea class="code"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-4 col-md-6 canvas-wrapper">
|
||||||
|
<div class="svg-image" width="300" height="300"></div>
|
||||||
|
<pre class="output"></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
function getLiveCodeOptions() {
|
||||||
|
return {
|
||||||
|
base_url: "{{ livecode_url }}",
|
||||||
|
runtime: "python",
|
||||||
|
files: LIVECODE_FILES, // loaded from livecode-files.js
|
||||||
|
command: ["python", "start.py"],
|
||||||
|
codemirror: true,
|
||||||
|
onMessage: {
|
||||||
|
image: function(editor, msg) {
|
||||||
|
const element = editor.parent.querySelector(".svg-image");
|
||||||
|
element.innerHTML = msg.image;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
var editorLookup = {};
|
||||||
|
|
||||||
|
$("pre.example, pre.exercise").each((i, e) => {
|
||||||
|
var code = $(e).text();
|
||||||
|
var template = document.querySelector('#livecode-template');
|
||||||
|
var clone = template.content.cloneNode(true);
|
||||||
|
|
||||||
|
$(e)
|
||||||
|
.wrap('<div></div>')
|
||||||
|
.hide()
|
||||||
|
.parent()
|
||||||
|
.append(clone)
|
||||||
|
.find("textarea.code")
|
||||||
|
.val(code);
|
||||||
|
|
||||||
|
if ($(e).hasClass("exercise")) {
|
||||||
|
var last_submitted = $(e).data("last-submitted");
|
||||||
|
if (last_submitted) {
|
||||||
|
$(e).parent().find(".last-submitted")
|
||||||
|
.data("timestamp", last_submitted)
|
||||||
|
.html(__("Submitted {0}", [comment_when(last_submitted)]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$(e).parent().find(".exercise-controls").remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
var editor = new LiveCodeEditor(e.parentElement, {
|
||||||
|
...getLiveCodeOptions(),
|
||||||
|
codemirror: true,
|
||||||
|
onMessage: {
|
||||||
|
image: function(editor, msg) {
|
||||||
|
const canvasElement = editor.parent.querySelector("div.svg-image");
|
||||||
|
canvasElement.innerHTML = msg.image;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(e).parent().find(".submit").on('click', function() {
|
||||||
|
var name = $(e).data("name");
|
||||||
|
let code = editor.codemirror.doc.getValue();
|
||||||
|
|
||||||
|
frappe.call("community.lms.api.submit_solution", {
|
||||||
|
"exercise": name,
|
||||||
|
"code": code
|
||||||
|
}).then(r => {
|
||||||
|
if (r.message.name) {
|
||||||
|
frappe.msgprint("Submitted successfully!");
|
||||||
|
|
||||||
|
let d = r.message.creation;
|
||||||
|
$(e).parent().find(".human-time").html(__("Submitted {0}", [comment_when(d)]));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".exercise-image").each((i, e) => {
|
||||||
|
var svg = JSON.parse($(e).data("image"));
|
||||||
|
$(e).html(svg);
|
||||||
|
});
|
||||||
|
|
||||||
|
$("pre.exercise").each((i, e) => {
|
||||||
|
var svg = JSON.parse($(e).data("image"));
|
||||||
|
$(e).parent().find(".svg-image").html(svg);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
.svg-image {
|
||||||
|
border: 5px solid #ddd;
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
|
width: 310px;
|
||||||
|
height: 310px;
|
||||||
|
}
|
||||||
|
.livecode-editor {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.livecode-editor-small .svg-image {
|
||||||
|
border: 5px solid #ddd;
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
|
width: 210px;
|
||||||
|
height: 210px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* work-in-progress styles for showing admonition */
|
||||||
|
.admonition {
|
||||||
|
border: 1px solid #aaa;
|
||||||
|
border-left: .5rem solid #888;
|
||||||
|
border-radius: .3em;
|
||||||
|
font-size: 0.9em;
|
||||||
|
margin: 1.5em 0;
|
||||||
|
padding: 0 0.5em;
|
||||||
|
}
|
||||||
|
.admonition-title {
|
||||||
|
padding: 0.5em 0px;
|
||||||
|
font-weight: bold;
|
||||||
|
padding-top:
|
||||||
|
}
|
||||||
|
</style>
|
||||||
8
community/templates/livecode/extension_header.html
Normal file
8
community/templates/livecode/extension_header.html
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<link rel="stylesheet" href="{{ livecode_url }}/static/codemirror/lib/codemirror.css">
|
||||||
|
|
||||||
|
<script src="{{ livecode_url }}/static/codemirror/lib/codemirror.js"></script>
|
||||||
|
<script src="{{ livecode_url }}/static/codemirror/mode/python/python.js"></script>
|
||||||
|
<script src="{{ livecode_url }}/static/codemirror/keymap/sublime.js"></script>
|
||||||
|
|
||||||
|
<script src="{{ livecode_url }}/static/codemirror/addon/edit/matchbrackets.js"></script>
|
||||||
|
<script src="{{ livecode_url }}/static/codemirror/addon/comment/comment.js"></script>
|
||||||
30
community/templates/quiz.html
Normal file
30
community/templates/quiz.html
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{% set last_submission = quiz.get_last_submission_details() %}
|
||||||
|
{% if last_submission %}
|
||||||
|
<div class="mb-5 pull-right">
|
||||||
|
<div class="text-muted">Last Submitted On: {{ frappe.utils.pretty_date(last_submission.creation) }}</div>
|
||||||
|
<div class="text-muted">Last Submission Score: {{ last_submission.score }}</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<h3 id="title" class="mb-5">{{ quiz.title }}</h3>
|
||||||
|
<form id="quiz-form">
|
||||||
|
{% for question in quiz.questions %}
|
||||||
|
<div class="question mb-5" data-question="{{ question.question }}"
|
||||||
|
data-multi="{{ question.multiple_correct_answers}}">
|
||||||
|
<p> {{ loop.index }}. {{ question.question }}</p>
|
||||||
|
{% set options = [question.option_1, question.option_2, question.option_3, question.option_4] %}
|
||||||
|
{% for option in options %}
|
||||||
|
{% if option %}
|
||||||
|
<div class="checkbox mb-2">
|
||||||
|
<input {% if question.multiple %} type="checkbox" {% else %} type="radio"
|
||||||
|
name="{{ question.question | urlencode }}" {% endif %} class="option" value="{{ option | urlencode }}">
|
||||||
|
<span class="label-area">{{ option }}</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
<button class="btn btn-secondary hide mb-5" id="try-again">Try Again</button>
|
||||||
|
<button class="btn btn-primary" id="submit-quiz">Submit</button>
|
||||||
|
<h4 class="success-message"></h4>
|
||||||
|
<h5 class="score text-muted"></h5>
|
||||||
|
</form>
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
<div class="container mt-5">
|
<div class="container mt-5">
|
||||||
{{ widgets.BatchTabs(course=course, membership=membership) }}
|
{{ widgets.BatchTabs(course=course, membership=membership) }}
|
||||||
<div class="course-details mt-5">
|
<div class="course-details mt-5">
|
||||||
{{ widgets.CourseOutline(course=course, batch=batch, show_link=True, show_progress=True) }}
|
{{ widgets.CourseOutline(course=course, batch=batch, show_link=membership, show_progress=True) }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if batch %}
|
{% if batch %}
|
||||||
|
|||||||
@@ -36,13 +36,13 @@
|
|||||||
<a href="/courses/{{ course.name }}">Checkout Course Details.</a>
|
<a href="/courses/{{ course.name }}">Checkout Course Details.</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if membership %}
|
||||||
{{ pagination(prev_chap, prev_url, next_chap, next_url) }}
|
{{ pagination(prev_chap, prev_url, next_chap, next_url) }}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
{% macro pagination(prev_chap, prev_url, next_chap, next_url) %}
|
{% macro pagination(prev_chap, prev_url, next_chap, next_url) %}
|
||||||
<div class="lesson-pagination">
|
<div class="lesson-pagination">
|
||||||
{% if prev_url %}
|
{% if prev_url %}
|
||||||
@@ -61,6 +61,11 @@
|
|||||||
|
|
||||||
{%- block script %}
|
{%- block script %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
var page_context = {{ page_context | tojson }};
|
||||||
|
</script>
|
||||||
|
|
||||||
{% for ext in page_extensions %}
|
{% for ext in page_extensions %}
|
||||||
{{ ext.render_footer() }}
|
{{ ext.render_footer() }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
frappe.ready(() => {
|
frappe.ready(() => {
|
||||||
|
|
||||||
|
/* Save Lesson Progress */
|
||||||
if ($(".title").attr("data-membership") && !$(".title").hasClass("is_mentor")) {
|
if ($(".title").attr("data-membership") && !$(".title").hasClass("is_mentor")) {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "community.lms.doctype.lesson.lesson.save_progress",
|
method: "community.lms.doctype.lesson.lesson.save_progress",
|
||||||
@@ -8,10 +10,60 @@ frappe.ready(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Save Current Lesson */
|
||||||
if ($(".title").attr("data-membership")) {
|
if ($(".title").attr("data-membership")) {
|
||||||
frappe.call("community.lms.api.save_current_lesson", {
|
frappe.call("community.lms.api.save_current_lesson", {
|
||||||
course_name: $(".title").attr("data-course"),
|
course_name: $(".title").attr("data-course"),
|
||||||
lesson_name: $(".title").attr("data-lesson")
|
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");
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -30,11 +30,17 @@ def get_context(context):
|
|||||||
next_ = outline.get_next(lesson_number)
|
next_ = outline.get_next(lesson_number)
|
||||||
context.prev_chap = get_chapter_title(course_name, prev_)
|
context.prev_chap = get_chapter_title(course_name, prev_)
|
||||||
context.next_chap = get_chapter_title(course_name, next_)
|
context.next_chap = get_chapter_title(course_name, next_)
|
||||||
context.next_url = context.course.get_learn_url(next_) + context.course.query_parameter
|
context.next_url = context.course.get_learn_url(next_) and context.course.get_learn_url(next_) + context.course.query_parameter
|
||||||
context.prev_url = context.course.get_learn_url(prev_) + context.course.query_parameter
|
context.prev_url = context.course.get_learn_url(prev_) and context.course.get_learn_url(prev_) + context.course.query_parameter
|
||||||
|
|
||||||
context.page_extensions = get_page_extensions()
|
context.page_extensions = get_page_extensions()
|
||||||
|
|
||||||
|
context.page_context = {
|
||||||
|
"course": context.course.name,
|
||||||
|
"batch": context.get("batch") and context.batch.name,
|
||||||
|
"lesson": context.lesson.name
|
||||||
|
}
|
||||||
|
|
||||||
def get_chapter_title(course_name, lesson_number):
|
def get_chapter_title(course_name, lesson_number):
|
||||||
if not lesson_number:
|
if not lesson_number:
|
||||||
return
|
return
|
||||||
@@ -49,7 +55,7 @@ def get_lesson_index(course, batch, user):
|
|||||||
return lesson and course.get_lesson_index(lesson)
|
return lesson and course.get_lesson_index(lesson)
|
||||||
|
|
||||||
def get_page_extensions():
|
def get_page_extensions():
|
||||||
default_value = ["community.community.plugins.PageExtension"]
|
default_value = ["community.plugins.PageExtension"]
|
||||||
classnames = frappe.get_hooks("community_lesson_page_extensions") or default_value
|
classnames = frappe.get_hooks("community_lesson_page_extensions") or default_value
|
||||||
extensions = [frappe.get_attr(name)() for name in classnames]
|
extensions = [frappe.get_attr(name)() for name in classnames]
|
||||||
return extensions
|
return extensions
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ def get_common_context(context):
|
|||||||
context.members = course.get_mentors(membership.batch) + course.get_students(membership.batch)
|
context.members = course.get_mentors(membership.batch) + course.get_students(membership.batch)
|
||||||
context.member_count = len(context.members)
|
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()
|
context.livecode_url = get_livecode_url()
|
||||||
|
|
||||||
def get_livecode_url():
|
def get_livecode_url():
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
{% block head_include %}
|
{% block head_include %}
|
||||||
<meta name="description" content="Courses" />
|
<meta name="description" content="Courses" />
|
||||||
<meta name="keywords" content="Courses {{course.title}}" />
|
<meta name="keywords" content="Courses {{course.title}}" />
|
||||||
|
<link rel="stylesheet" href="/assets/frappe/css/font-awesome.css">
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
@@ -30,7 +31,7 @@
|
|||||||
{{ CourseDescription(course) }}
|
{{ CourseDescription(course) }}
|
||||||
{{ widgets.InstructorSection(instructor=course.get_instructor()) }}
|
{{ widgets.InstructorSection(instructor=course.get_instructor()) }}
|
||||||
{{ BatchSection(course) }}
|
{{ BatchSection(course) }}
|
||||||
{{ widgets.CourseOutline(course=course, show_link=False) }}
|
{{ widgets.CourseOutline(course=course, show_link=membership) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ def get_context(context):
|
|||||||
raise frappe.Redirect
|
raise frappe.Redirect
|
||||||
|
|
||||||
context.course = course
|
context.course = course
|
||||||
context.course.query_parameter = ""
|
membership = course.get_membership(frappe.session.user)
|
||||||
if not course.is_mentor(frappe.session.user):
|
context.course.query_parameter = "?batch=" + membership.batch if membership and membership.batch else ""
|
||||||
batch = course.get_membership(frappe.session.user)
|
context.membership = membership
|
||||||
if batch:
|
if not course.is_mentor(frappe.session.user) and membership:
|
||||||
frappe.local.flags.redirect_location = f"/courses/{course.name}/learn"
|
frappe.local.flags.redirect_location = f"/courses/{course.name}/learn"
|
||||||
raise frappe.Redirect
|
raise frappe.Redirect
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
{% macro LiveCodeEditorLarge(name, code) %}
|
{% macro LiveCodeEditorLarge(name, code) %}
|
||||||
<div class="livecode-editor livecode-editor-large" id="editor-{{name}}">
|
<div class="livecode-editor livecode-editor-large" id="editor-{{name}}">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
{% extends "templates/base.html" %}
|
|
||||||
{% from "www/macros/livecode.html" import LiveCodeEditor, LiveCodeEditorJS %}
|
|
||||||
|
|
||||||
{% block title %}Sketches{% endblock %}
|
|
||||||
{% block head_include %}
|
|
||||||
<meta name="description" content="Sketches" />
|
|
||||||
<meta name="keywords" content="sketches" />
|
|
||||||
<link rel="stylesheet" href="/assets/css/lms.css">
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<section class="top-section" style="padding: 1rem 0rem;">
|
|
||||||
<div class='container pb-5'>
|
|
||||||
<h1>Recent Sketches</h1>
|
|
||||||
|
|
||||||
<a href="/sketches/new" class="btn btn-primary">Create a New Sketch</a>
|
|
||||||
</div>
|
|
||||||
<div class='container'>
|
|
||||||
<div class="row">
|
|
||||||
{% for sketch in sketches %}
|
|
||||||
<div class="col-md-3">
|
|
||||||
{{ widgets.SketchTeaser(sketch=sketch) }}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import frappe
|
|
||||||
from community.lms.models import Sketch
|
|
||||||
|
|
||||||
def get_context(context):
|
|
||||||
context.no_cache = 1
|
|
||||||
context.sketches = Sketch.get_recent_sketches()
|
|
||||||
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
{% extends "templates/base.html" %}
|
|
||||||
{% from "www/macros/livecode.html" import LiveCodeEditorLarge, LiveCodeEditorJS with context %}
|
|
||||||
|
|
||||||
{% block title %}{{sketch.title}}{% endblock %}
|
|
||||||
{% block head_include %}
|
|
||||||
<meta name="description" content="Sketch {{sketch.title}}" />
|
|
||||||
<meta name="keywords" content="sketch {{sketch.title}}" />
|
|
||||||
<style>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="{{ livecode_url }}/static/codemirror/lib/codemirror.css">
|
|
||||||
<link rel="stylesheet" href="/assets/css/lms.css">
|
|
||||||
|
|
||||||
<script src="{{ livecode_url }}/static/codemirror/lib/codemirror.js"></script>
|
|
||||||
<script src="{{ livecode_url }}/static/codemirror/mode/python/python.js"></script>
|
|
||||||
<script src="{{ livecode_url }}/static/codemirror/keymap/sublime.js"></script>
|
|
||||||
|
|
||||||
<script src="{{ livecode_url }}/static/codemirror/addon/edit/matchbrackets.js"></script>
|
|
||||||
<script src="{{ livecode_url }}/static/codemirror/addon/comment/comment.js"></script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<section class="top-section" style="padding: 1rem 0rem;">
|
|
||||||
<div class='container pb-5'>
|
|
||||||
<nav aria-label="breadcrumb">
|
|
||||||
<ol class="breadcrumb">
|
|
||||||
<li class="breadcrumb-item" aria-current="page"><a href="/sketches">Sketches</a></li>
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="sketch-header">
|
|
||||||
{% if editable %}
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="col-lg-8 col-md-6">
|
|
||||||
<input type="text" id="sketch-title" name="title" class="form-control" value="{{ sketch.title }}">
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-4 col-md-6">
|
|
||||||
<button type="submit" id="sketch-save" class="btn-save btn btn-primary">Save</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<h1 class="sketch-title">{{sketch.title}}</h1>
|
|
||||||
<div class="sketch-owner-wrapper">By <span class="sketch-owner">{{sketch.get_owner_name()}}</span></div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if sketch.is_new() and not editable %}
|
|
||||||
<div class="alert alert-warning">
|
|
||||||
Please login to save this sketch.
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="sketch-editor">
|
|
||||||
{{LiveCodeEditorLarge(sketch.name, sketch.code) }}
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{%- block script %}
|
|
||||||
{{ super() }}
|
|
||||||
{{ LiveCodeEditorJS() }}
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
|
||||||
var sketch_name = {{ sketch.name | tojson }};
|
|
||||||
|
|
||||||
function saveSketch() {
|
|
||||||
var title = $("#sketch-title").val()
|
|
||||||
var code = livecodeEditors[0].codemirror.doc.getValue()
|
|
||||||
frappe.call('community.lms.doctype.lms_sketch.lms_sketch.save_sketch', {
|
|
||||||
name: sketch_name,
|
|
||||||
title: title,
|
|
||||||
code: code
|
|
||||||
})
|
|
||||||
.then(r => {
|
|
||||||
var msg = r.message;
|
|
||||||
if (!msg.ok) {
|
|
||||||
var error = msg.error || "Save failed."
|
|
||||||
frappe.msgprint({
|
|
||||||
"title": "Error",
|
|
||||||
"indicator": "red",
|
|
||||||
"message": error
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (msg.status == "created") {
|
|
||||||
var path = "/sketches/sketch?sketch=" + msg.name;
|
|
||||||
var url = window.location.protocol + "//" + window.location.host + path
|
|
||||||
window.history.pushState({path: url}, '', url);
|
|
||||||
sketch_name = name;
|
|
||||||
|
|
||||||
frappe.msgprint({
|
|
||||||
"title": "Notification",
|
|
||||||
"indicator": "green",
|
|
||||||
"message": "New sketch has been saved!"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (msg.status == "updated") {
|
|
||||||
frappe.msgprint({
|
|
||||||
"title": "Notification",
|
|
||||||
"indicator": "green",
|
|
||||||
"message": "The sketch has been saved!"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
$(function() {
|
|
||||||
$("#sketch-save").click(function() {
|
|
||||||
saveSketch();
|
|
||||||
});
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
{%- endblock %}
|
|
||||||
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import frappe
|
|
||||||
|
|
||||||
def get_context(context):
|
|
||||||
context.no_cache = 1
|
|
||||||
|
|
||||||
try:
|
|
||||||
sketch_id = frappe.form_dict["sketch"]
|
|
||||||
except KeyError:
|
|
||||||
context.template = "www/404.html"
|
|
||||||
return
|
|
||||||
|
|
||||||
sketch = get_sketch(sketch_id)
|
|
||||||
if not sketch:
|
|
||||||
context.template = "www/404.html"
|
|
||||||
return
|
|
||||||
|
|
||||||
context.sketch = sketch
|
|
||||||
context.livecode_url = get_livecode_url()
|
|
||||||
context.editable = is_editable(context.sketch, frappe.session.user)
|
|
||||||
|
|
||||||
def is_editable(sketch, user):
|
|
||||||
if sketch.is_new():
|
|
||||||
# new sketches can be editable by any logged in user
|
|
||||||
return user != "Guest"
|
|
||||||
else:
|
|
||||||
# existing sketches are editable by the owner
|
|
||||||
return sketch.owner == user
|
|
||||||
|
|
||||||
def get_livecode_url():
|
|
||||||
doc = frappe.get_doc("LMS Settings")
|
|
||||||
return doc.livecode_url
|
|
||||||
|
|
||||||
def get_sketch(sketch_id):
|
|
||||||
if sketch_id == 'new':
|
|
||||||
sketch = frappe.new_doc('LMS Sketch')
|
|
||||||
sketch.name = "new"
|
|
||||||
sketch.title = "New Sketch"
|
|
||||||
sketch.code = "circle(100, 100, 50)"
|
|
||||||
return sketch
|
|
||||||
|
|
||||||
try:
|
|
||||||
name = "SKETCH-" + sketch_id
|
|
||||||
return frappe.get_doc('LMS Sketch', name)
|
|
||||||
except frappe.exceptions.DoesNotExistError:
|
|
||||||
return
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user