From d61acb552a9b786c02c960e430fb5179b028ef08 Mon Sep 17 00:00:00 2001 From: Anand Chitipothu Date: Wed, 19 May 2021 20:00:19 +0530 Subject: [PATCH 1/9] feat: added Exercise and Exercise Submission doctypes Also: - added methods to submit an exercise and get the submission for a user - added test cases Issue #90 --- community/lms/doctype/exercise/__init__.py | 0 community/lms/doctype/exercise/exercise.js | 8 ++ community/lms/doctype/exercise/exercise.json | 99 +++++++++++++++++++ community/lms/doctype/exercise/exercise.py | 44 +++++++++ .../lms/doctype/exercise/test_exercise.py | 37 +++++++ .../doctype/exercise_submission/__init__.py | 0 .../exercise_submission.js | 8 ++ .../exercise_submission.json | 59 +++++++++++ .../exercise_submission.py | 8 ++ .../test_exercise_submission.py | 8 ++ 10 files changed, 271 insertions(+) create mode 100644 community/lms/doctype/exercise/__init__.py create mode 100644 community/lms/doctype/exercise/exercise.js create mode 100644 community/lms/doctype/exercise/exercise.json create mode 100644 community/lms/doctype/exercise/exercise.py create mode 100644 community/lms/doctype/exercise/test_exercise.py create mode 100644 community/lms/doctype/exercise_submission/__init__.py create mode 100644 community/lms/doctype/exercise_submission/exercise_submission.js create mode 100644 community/lms/doctype/exercise_submission/exercise_submission.json create mode 100644 community/lms/doctype/exercise_submission/exercise_submission.py create mode 100644 community/lms/doctype/exercise_submission/test_exercise_submission.py diff --git a/community/lms/doctype/exercise/__init__.py b/community/lms/doctype/exercise/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/community/lms/doctype/exercise/exercise.js b/community/lms/doctype/exercise/exercise.js new file mode 100644 index 00000000..740634b4 --- /dev/null +++ b/community/lms/doctype/exercise/exercise.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, FOSS United and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Exercise', { + // refresh: function(frm) { + + // } +}); diff --git a/community/lms/doctype/exercise/exercise.json b/community/lms/doctype/exercise/exercise.json new file mode 100644 index 00000000..a54f84af --- /dev/null +++ b/community/lms/doctype/exercise/exercise.json @@ -0,0 +1,99 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2021-05-19 17:43:39.923430", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "description", + "code", + "answer", + "column_break_4", + "course", + "hints", + "tests", + "image" + ], + "fields": [ + { + "fieldname": "title", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Title" + }, + { + "fieldname": "course", + "fieldtype": "Link", + "label": "Course", + "options": "LMS Course" + }, + { + "columns": 4, + "fieldname": "description", + "fieldtype": "Markdown Editor", + "in_list_view": 1, + "label": "Description" + }, + { + "columns": 4, + "fieldname": "answer", + "fieldtype": "Code", + "label": "Answer" + }, + { + "fieldname": "tests", + "fieldtype": "Code", + "label": "Tests" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "columns": 4, + "fieldname": "hints", + "fieldtype": "Markdown Editor", + "label": "Hints" + }, + { + "columns": 4, + "fieldname": "code", + "fieldtype": "Code", + "label": "Code" + }, + { + "fieldname": "image", + "fieldtype": "Code", + "label": "Image", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-05-19 18:27:40.475515", + "modified_by": "Administrator", + "module": "LMS", + "name": "Exercise", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "search_fields": "title", + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "title", + "track_changes": 1 +} \ No newline at end of file diff --git a/community/lms/doctype/exercise/exercise.py b/community/lms/doctype/exercise/exercise.py new file mode 100644 index 00000000..ae9c44eb --- /dev/null +++ b/community/lms/doctype/exercise/exercise.py @@ -0,0 +1,44 @@ +# Copyright (c) 2021, FOSS United and contributors +# For license information, please see license.txt + +import frappe +from frappe.model.document import Document + +class Exercise(Document): + def get_user_submission(self): + """Returns the latest submission for this user. + """ + user = frappe.session.user + if not user or user == "Guest": + return + + result = frappe.get_all('Exercise Submission', + fields="*", + filters={ + "owner": user, + "exercise": self.name + }, + order_by="creation desc", + page_length=1) + if result: + return result[0] + + def submit(self, code): + """Submits the given code as solution to exercise. + """ + user = frappe.session.user + if not user or user == "Guest": + return + + old_submission = self.get_user_submission() + if old_submission and old_submission.solution == code: + return old_submission + + doc = frappe.get_doc( + doctype="Exercise Submission", + exercise=self.name, + exercise_title=self.title, + solution=code) + doc.insert() + return doc + diff --git a/community/lms/doctype/exercise/test_exercise.py b/community/lms/doctype/exercise/test_exercise.py new file mode 100644 index 00000000..02dc2c38 --- /dev/null +++ b/community/lms/doctype/exercise/test_exercise.py @@ -0,0 +1,37 @@ +# Copyright (c) 2021, FOSS United and Contributors +# See license.txt + +import frappe +import unittest + +class TestExercise(unittest.TestCase): + def setUp(self): + frappe.db.sql('delete from `tabExercise Submission`') + frappe.db.sql('delete from `tabExercise`') + + def new_exercise(self): + e = frappe.get_doc({ + "doctype": "Exercise", + "name": "test-problem", + "title": "Test Problem", + "description": "draw a circle", + "code": "# draw a single cicle", + "answer": ( + "# draw a single circle\n" + + "circle(100, 100, 50)") + }) + e.insert() + return e + + def test_exercise(self): + e = self.new_exercise() + assert e.get_user_submission() is None + + def test_exercise_submission(self): + e = self.new_exercise() + submission = e.submit("circle(100, 100, 50)") + assert submission is not None + + user_submission = e.get_user_submission() + assert user_submission is not None + assert user_submission.name == submission.name diff --git a/community/lms/doctype/exercise_submission/__init__.py b/community/lms/doctype/exercise_submission/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/community/lms/doctype/exercise_submission/exercise_submission.js b/community/lms/doctype/exercise_submission/exercise_submission.js new file mode 100644 index 00000000..5f1d8399 --- /dev/null +++ b/community/lms/doctype/exercise_submission/exercise_submission.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, FOSS United and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Exercise Submission', { + // refresh: function(frm) { + + // } +}); diff --git a/community/lms/doctype/exercise_submission/exercise_submission.json b/community/lms/doctype/exercise_submission/exercise_submission.json new file mode 100644 index 00000000..ab26ff8e --- /dev/null +++ b/community/lms/doctype/exercise_submission/exercise_submission.json @@ -0,0 +1,59 @@ +{ + "actions": [], + "creation": "2021-05-19 11:41:18.108316", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "exercise", + "solution", + "exercise_title" + ], + "fields": [ + { + "fieldname": "exercise", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Exercise", + "options": "Exercise" + }, + { + "fieldname": "solution", + "fieldtype": "Code", + "in_list_view": 1, + "label": "Solution" + }, + { + "fetch_from": "exercise.title", + "fieldname": "exercise_title", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Exercise Title", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-05-19 19:41:55.852069", + "modified_by": "Administrator", + "module": "LMS", + "name": "Exercise 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/exercise_submission/exercise_submission.py b/community/lms/doctype/exercise_submission/exercise_submission.py new file mode 100644 index 00000000..fd631eb8 --- /dev/null +++ b/community/lms/doctype/exercise_submission/exercise_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 ExerciseSubmission(Document): + pass diff --git a/community/lms/doctype/exercise_submission/test_exercise_submission.py b/community/lms/doctype/exercise_submission/test_exercise_submission.py new file mode 100644 index 00000000..eed851a5 --- /dev/null +++ b/community/lms/doctype/exercise_submission/test_exercise_submission.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, FOSS United and Contributors +# See license.txt + +# import frappe +import unittest + +class TestExerciseSubmission(unittest.TestCase): + pass From 6f7011ca58f5b4384c7f4222e602f97594709a60 Mon Sep 17 00:00:00 2001 From: Anand Chitipothu Date: Wed, 19 May 2021 20:04:20 +0530 Subject: [PATCH 2/9] feat: integrated exercises into lessons - an exercise can now be added to a lesson - it is rendered using livecode editor with submit button - remembers the submitted code and shows the submission time Issue #90 --- community/lms/api.py | 10 ++++ community/lms/doctype/lesson/lesson.py | 1 + .../lms/doctype/lms_section/lms_section.json | 10 +++- .../lms/doctype/lms_section/lms_section.py | 4 ++ community/lms/widgets/Exercise.html | 14 +++++ community/www/courses/learn/index.html | 10 +++- community/www/macros/livecode.html | 58 +++++++++++++++++-- 7 files changed, 99 insertions(+), 8 deletions(-) create mode 100644 community/lms/widgets/Exercise.html diff --git a/community/lms/api.py b/community/lms/api.py index ab69ede4..879c2a05 100644 --- a/community/lms/api.py +++ b/community/lms/api.py @@ -21,3 +21,13 @@ def get_section(name): """ doc = frappe.get_doc("LMS Section", name) return doc and doc.as_dict() + +@frappe.whitelist() +def submit_solution(exercise, code): + """Submits a solution. + """ + ex = frappe.get_doc("Exercise", exercise) + if not ex: + return + doc = ex.submit(code) + return {"name": doc.name, "creation": doc.creation} diff --git a/community/lms/doctype/lesson/lesson.py b/community/lms/doctype/lesson/lesson.py index b1105240..87960951 100644 --- a/community/lms/doctype/lesson/lesson.py +++ b/community/lms/doctype/lesson/lesson.py @@ -18,6 +18,7 @@ class Lesson(Document): def make_lms_section(self, index, section): s = frappe.new_doc('LMS Section', parent_doc=self, parentfield='sections') s.type = section.type + s.id = section.id s.label = section.label s.contents = section.contents s.index = index diff --git a/community/lms/doctype/lms_section/lms_section.json b/community/lms/doctype/lms_section/lms_section.json index 4a275d32..3b056485 100644 --- a/community/lms/doctype/lms_section/lms_section.json +++ b/community/lms/doctype/lms_section/lms_section.json @@ -9,7 +9,8 @@ "contents", "code", "attrs", - "index" + "index", + "id" ], "fields": [ { @@ -43,12 +44,17 @@ "fieldname": "index", "fieldtype": "Int", "label": "Index" + }, + { + "fieldname": "id", + "fieldtype": "Data", + "label": "id" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-03-12 17:56:23.118854", + "modified": "2021-05-19 18:55:26.019625", "modified_by": "Administrator", "module": "LMS", "name": "LMS Section", diff --git a/community/lms/doctype/lms_section/lms_section.py b/community/lms/doctype/lms_section/lms_section.py index f51fec69..65c33f46 100644 --- a/community/lms/doctype/lms_section/lms_section.py +++ b/community/lms/doctype/lms_section/lms_section.py @@ -10,6 +10,10 @@ 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_latest_code_for_user(self): """Returns the latest code for the logged in user. """ diff --git a/community/lms/widgets/Exercise.html b/community/lms/widgets/Exercise.html new file mode 100644 index 00000000..af3ba383 --- /dev/null +++ b/community/lms/widgets/Exercise.html @@ -0,0 +1,14 @@ +{% from "www/macros/livecode.html" import LiveCodeEditorJS, LiveCodeEditor with context %} + +
+

{{ exercise.title }}

+
{{frappe.utils.md_to_html(exercise.description)}}
+ + {% set submission = exercise.get_user_submission() %} + + {{ LiveCodeEditor(exercise.name, + code=exercise.code, + reset_code=exercise.code, + is_exercise=True, + last_submitted=submission and submission.creation) }} +
diff --git a/community/www/courses/learn/index.html b/community/www/courses/learn/index.html index 4804f508..3dbd5adf 100644 --- a/community/www/courses/learn/index.html +++ b/community/www/courses/learn/index.html @@ -55,8 +55,14 @@ {% macro render_section(s) %} {% if s.type == "text" %} {{ render_section_text(s) }} - {% elif s.type == "example" or s.type == "code" or s.type == "exercise" %} - {{ LiveCodeEditor(s.name, s.get_latest_code_for_user(), s.type=="exercise", "2 hours ago") }} + {% elif s.type == "example" or s.type == "code" %} + {{ LiveCodeEditor(s.name, + code=s.get_latest_code_for_user(), + reset_code=s.contents, + is_exercise=False) + }} + {% elif s.type == "exercise" %} + {{ widgets.Exercise(exercise=s.get_exercise())}} {% else %}
Unknown section type: {{s.type}}
{% endif %} diff --git a/community/www/macros/livecode.html b/community/www/macros/livecode.html index 94502e53..a4ec142f 100644 --- a/community/www/macros/livecode.html +++ b/community/www/macros/livecode.html @@ -24,7 +24,7 @@ {% endmacro %} -{% macro LiveCodeEditor(name, code, is_exercise, last_submitted) %} +{% macro LiveCodeEditor(name, code, reset_code, is_exercise=False, last_submitted=None) %}
@@ -34,10 +34,13 @@ {% if is_exercise %} {% if last_submitted %} - Submitted on {{last_submitted}} + {% endif %} {% endif %}
+
+
{{reset_code}}
+
@@ -59,20 +62,67 @@ {% macro LiveCodeEditorJS(name, code) %} + + + + + + + + {% endmacro %} From 646a7b723fff1f75d2085d89a2587897f2501b87 Mon Sep 17 00:00:00 2001 From: Anand Chitipothu Date: Wed, 19 May 2021 20:12:35 +0530 Subject: [PATCH 3/9] fix: fix broken pagination links --- community/www/courses/learn/index.html | 27 ++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/community/www/courses/learn/index.html b/community/www/courses/learn/index.html index 3dbd5adf..24cf5504 100644 --- a/community/www/courses/learn/index.html +++ b/community/www/courses/learn/index.html @@ -26,14 +26,7 @@
-
- {% if prev_url %} - ← Prev - {% endif %} - {% if next_url %} - Next → - {% endif %} -
+ {{ pagination(prev_url, next_url) }}

{{ lesson.title }}

@@ -43,10 +36,8 @@
{% endfor %} - + {{ pagination(prev_url, next_url) }} +
{% endblock %} @@ -76,6 +67,18 @@ {% endmacro %} +{% macro pagination(prev_url, next_url) %} +
+ {% if prev_url %} + ← Prev + {% endif %} + {% if next_url %} + Next → + {% endif %} +
+
+{% endmacro %} + {%- block script %} {{ super() }} {{ LiveCodeEditorJS() }} From a67ad67be1344644f8c39139cf504855cbba91f1 Mon Sep 17 00:00:00 2001 From: Anand Chitipothu Date: Thu, 20 May 2021 12:09:12 +0530 Subject: [PATCH 4/9] feat: show image for the exercise generate the image from the answer and display it along with description. The image is geneated when the exercise is saved. --- community/lms/doctype/exercise/exercise.py | 4 ++++ community/lms/doctype/lms_sketch/livecode.py | 13 +++++++++++++ community/lms/widgets/Exercise.html | 13 +++++++++++++ community/public/css/style.less | 11 +++++++++++ 4 files changed, 41 insertions(+) diff --git a/community/lms/doctype/exercise/exercise.py b/community/lms/doctype/exercise/exercise.py index ae9c44eb..b9e9246e 100644 --- a/community/lms/doctype/exercise/exercise.py +++ b/community/lms/doctype/exercise/exercise.py @@ -3,8 +3,12 @@ import frappe from frappe.model.document import Document +from ..lms_sketch.livecode import livecode_to_svg class Exercise(Document): + def before_save(self): + self.image = livecode_to_svg(None, self.answer) + def get_user_submission(self): """Returns the latest submission for this user. """ diff --git a/community/lms/doctype/lms_sketch/livecode.py b/community/lms/doctype/lms_sketch/livecode.py index 8ea05ddd..ab5d8dc5 100644 --- a/community/lms/doctype/lms_sketch/livecode.py +++ b/community/lms/doctype/lms_sketch/livecode.py @@ -4,6 +4,7 @@ import websocket import json from .svg import SVG import frappe +from urllib.parse import urlparse # Files to pass to livecode server # The same code is part of livecode-canvas.js @@ -60,9 +61,21 @@ def clear(): clear() ''' +def get_livecode_url(): + doc = frappe.get_cached_doc("LMS Settings") + return doc.livecode_url + +def get_livecode_ws_url(): + url = urlparse(get_livecode_url()) + protocol = "wss" if url.scheme == "https" else "ws" + return protocol + "://" + url.netloc + "/livecode" + def livecode_to_svg(livecode_ws_url, code, *, timeout=3): """Renders the code as svg. """ + if livecode_ws_url is None: + livecode_ws_url = get_livecode_ws_url() + try: ws = websocket.WebSocket() ws.settimeout(timeout) diff --git a/community/lms/widgets/Exercise.html b/community/lms/widgets/Exercise.html index af3ba383..7e075058 100644 --- a/community/lms/widgets/Exercise.html +++ b/community/lms/widgets/Exercise.html @@ -4,6 +4,10 @@

{{ exercise.title }}

{{frappe.utils.md_to_html(exercise.description)}}
+ {% if exercise.image %} +
{{exercise.image}}
+ {% endif %} + {% set submission = exercise.get_user_submission() %} {{ LiveCodeEditor(exercise.name, @@ -12,3 +16,12 @@ is_exercise=True, last_submitted=submission and submission.creation) }} + + diff --git a/community/public/css/style.less b/community/public/css/style.less index b0800ff3..c2d05b19 100644 --- a/community/public/css/style.less +++ b/community/public/css/style.less @@ -306,3 +306,14 @@ section.lightgray { .lesson-page { margin: 20px 0px; } + +.lesson-pagination { + clear: both; +} + +.exercise-image svg { + width: 200px; + height: 200px; + border: 1px solid #ddd; + margin-bottom: 20px; +} From 8c889ffb92f25a00d45a7589065321f2a0db59e4 Mon Sep 17 00:00:00 2001 From: Anand Chitipothu Date: Thu, 20 May 2021 13:12:19 +0530 Subject: [PATCH 5/9] chore: switched to new build system for css. - added community.bundle.less file to create css bundle - removed the obsolete build.json in favor of new build system - updated the paths in the books - removed the imported urls from css files (the new build system doesn't support that) - removed obsolete lms.css --- community/hooks.py | 6 ++--- community/lms/widgets/Exercise.html | 9 -------- community/public/build.json | 14 ------------ community/public/css/community.bundle.less | 4 ++++ community/public/css/lms.css | 26 ---------------------- community/public/css/style.css | 4 +--- community/public/css/style.less | 5 ----- 7 files changed, 8 insertions(+), 60 deletions(-) delete mode 100644 community/public/build.json create mode 100644 community/public/css/community.bundle.less delete mode 100644 community/public/css/lms.css diff --git a/community/hooks.py b/community/hooks.py index 8868509c..bf047f2b 100644 --- a/community/hooks.py +++ b/community/hooks.py @@ -18,11 +18,11 @@ app_logo_url = APP_LOGO_URL # ------------------ # include js, css files in header of desk.html -app_include_css = "/assets/community/css/community.css" -app_include_js = "/assets/community/js/community.js" +# app_include_css = "/assets/community/css/community.css" +# app_include_js = "/assets/community/js/community.js" # include js, css files in header of web template -web_include_css = "/assets/css/community.css" +web_include_css = "community.bundle.css" # web_include_css = "/assets/community/css/community.css" # web_include_js = "/assets/community/js/community.js" diff --git a/community/lms/widgets/Exercise.html b/community/lms/widgets/Exercise.html index 7e075058..b43410b4 100644 --- a/community/lms/widgets/Exercise.html +++ b/community/lms/widgets/Exercise.html @@ -16,12 +16,3 @@ is_exercise=True, last_submitted=submission and submission.creation) }} - - diff --git a/community/public/build.json b/community/public/build.json deleted file mode 100644 index 06a54a29..00000000 --- a/community/public/build.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "css/lms.css": [ - "public/css/lms.css" - ], - "css/community.css": [ - "public/css/style.css", - "public/css/vars.css", - "public/css/style.less" - ], - "js/livecode-canvas.js": [ - "public/js/livecode-canvas.js" - ] - -} diff --git a/community/public/css/community.bundle.less b/community/public/css/community.bundle.less new file mode 100644 index 00000000..66652c93 --- /dev/null +++ b/community/public/css/community.bundle.less @@ -0,0 +1,4 @@ + +@import "./style.css"; +@import "./vars.css"; +@import "./style.less"; diff --git a/community/public/css/lms.css b/community/public/css/lms.css deleted file mode 100644 index 266295d0..00000000 --- a/community/public/css/lms.css +++ /dev/null @@ -1,26 +0,0 @@ - -.heading { - background: #eee; - padding: 10px; - clear: both; - color: #212529; - border: 1px solid #ddd; -} - -.sketch-header h1 { - font-size: 1.5em; - margin-bottom: 0px; -} - -.sketch-header { - margin-bottom: 1em; -} - -.sketch-card .sketch-title a { - font-weight: bold; - color: inherit; -} - -.hidden { - display: none; -} diff --git a/community/public/css/style.css b/community/public/css/style.css index 1462ebf4..8c3fb588 100644 --- a/community/public/css/style.css +++ b/community/public/css/style.css @@ -1,5 +1,3 @@ -@import url("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.4.1/font/bootstrap-icons.css"); -@import url("https://use.fontawesome.com/releases/v5.13.0/css/all.css"); :root { --c1: #fefae0; @@ -26,7 +24,7 @@ --send-message: var(--c7); --received-message: var(--c8); - --primary-color: #08B74F; + --primary-color: #08B74F !important; } body { diff --git a/community/public/css/style.less b/community/public/css/style.less index c2d05b19..e92d02ef 100644 --- a/community/public/css/style.less +++ b/community/public/css/style.less @@ -1,9 +1,4 @@ @primary-color: #08B74F; -@import url('https://rsms.me/inter/inter.css'); - -body { - font-family: "Inter", sans-serif; -} h2 { margin: 20px 0px; From 34e993cf86bcca523cce8d5b1a37488ab362c6d9 Mon Sep 17 00:00:00 2001 From: Anand Chitipothu Date: Thu, 20 May 2021 13:27:30 +0530 Subject: [PATCH 6/9] refactor: added lesson to exercise usualy to know which lesson an exercise is part of by looking at the exercise. --- community/lms/doctype/exercise/exercise.json | 15 +++++++++++---- community/lms/doctype/lesson/lesson.py | 5 +++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/community/lms/doctype/exercise/exercise.json b/community/lms/doctype/exercise/exercise.json index a54f84af..33c7151e 100644 --- a/community/lms/doctype/exercise/exercise.json +++ b/community/lms/doctype/exercise/exercise.json @@ -14,7 +14,8 @@ "course", "hints", "tests", - "image" + "image", + "lesson" ], "fields": [ { @@ -32,7 +33,7 @@ { "columns": 4, "fieldname": "description", - "fieldtype": "Markdown Editor", + "fieldtype": "Small Text", "in_list_view": 1, "label": "Description" }, @@ -54,7 +55,7 @@ { "columns": 4, "fieldname": "hints", - "fieldtype": "Markdown Editor", + "fieldtype": "Small Text", "label": "Hints" }, { @@ -68,11 +69,17 @@ "fieldtype": "Code", "label": "Image", "read_only": 1 + }, + { + "fieldname": "lesson", + "fieldtype": "Link", + "label": "Lesson", + "options": "Lesson" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-05-19 18:27:40.475515", + "modified": "2021-05-20 13:23:12.340928", "modified_by": "Administrator", "module": "LMS", "name": "Exercise", diff --git a/community/lms/doctype/lesson/lesson.py b/community/lms/doctype/lesson/lesson.py index 87960951..c544f774 100644 --- a/community/lms/doctype/lesson/lesson.py +++ b/community/lms/doctype/lesson/lesson.py @@ -11,6 +11,11 @@ class Lesson(Document): def before_save(self): sections = SectionParser().parse(self.body or "") self.sections = [self.make_lms_section(i, s) for i, s in enumerate(sections)] + for s in self.sections: + if s.type == "exercise": + e = s.get_exercise() + e.lesson = self.name + e.save() def get_sections(self): return sorted(self.get('sections'), key=lambda s: s.index) From 6407b24324ccc1fbe12379868eb4e1eea852111f Mon Sep 17 00:00:00 2001 From: Anand Chitipothu Date: Thu, 20 May 2021 15:07:14 +0530 Subject: [PATCH 7/9] feat: added course, batch and lesson to exercise submission Useful to find all the submissions for a batch/lesson. --- community/lms/api.py | 3 ++ community/lms/doctype/exercise/exercise.py | 6 ++++ .../exercise_submission.json | 28 +++++++++++++++++-- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/community/lms/api.py b/community/lms/api.py index 879c2a05..3a7bd1b0 100644 --- a/community/lms/api.py +++ b/community/lms/api.py @@ -25,6 +25,9 @@ def get_section(name): @frappe.whitelist() def submit_solution(exercise, code): """Submits a solution. + + @exerecise: name of the exercise to submit + @code: solution to the exercise """ ex = frappe.get_doc("Exercise", exercise) if not ex: diff --git a/community/lms/doctype/exercise/exercise.py b/community/lms/doctype/exercise/exercise.py index b9e9246e..313165cf 100644 --- a/community/lms/doctype/exercise/exercise.py +++ b/community/lms/doctype/exercise/exercise.py @@ -38,10 +38,16 @@ class Exercise(Document): if old_submission and old_submission.solution == code: return old_submission + course = frappe.get_doc("LMS Course", self.course) + batch = course.get_student_batch(user) + doc = frappe.get_doc( doctype="Exercise Submission", exercise=self.name, exercise_title=self.title, + course=self.course, + lesson=self.lesson, + batch=batch and batch.name, solution=code) doc.insert() return doc diff --git a/community/lms/doctype/exercise_submission/exercise_submission.json b/community/lms/doctype/exercise_submission/exercise_submission.json index ab26ff8e..de7541ea 100644 --- a/community/lms/doctype/exercise_submission/exercise_submission.json +++ b/community/lms/doctype/exercise_submission/exercise_submission.json @@ -7,7 +7,10 @@ "field_order": [ "exercise", "solution", - "exercise_title" + "exercise_title", + "course", + "batch", + "lesson" ], "fields": [ { @@ -30,11 +33,32 @@ "in_list_view": 1, "label": "Exercise Title", "read_only": 1 + }, + { + "fetch_from": "exercise.course", + "fieldname": "course", + "fieldtype": "Link", + "label": "Course", + "options": "LMS Course", + "read_only": 1 + }, + { + "fieldname": "batch", + "fieldtype": "Link", + "label": "Batch", + "options": "LMS Batch" + }, + { + "fetch_from": "exercise.lesson", + "fieldname": "lesson", + "fieldtype": "Link", + "label": "Lesson", + "options": "Lesson" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-05-19 19:41:55.852069", + "modified": "2021-05-20 13:30:16.349278", "modified_by": "Administrator", "module": "LMS", "name": "Exercise Submission", From 0859afdf3465bd3dfa32485533ebf0e272fd30c1 Mon Sep 17 00:00:00 2001 From: Anand Chitipothu Date: Thu, 20 May 2021 15:08:12 +0530 Subject: [PATCH 8/9] style: fixed the styles of the sidebar --- community/www/courses/about/index.html | 2 ++ community/www/courses/discuss/index.html | 2 ++ community/www/courses/learn/index.html | 1 + community/www/courses/members/index.html | 1 + community/www/courses/schedule/index.html | 1 + community/www/macros/sidebar.html | 14 +++++++------- 6 files changed, 14 insertions(+), 7 deletions(-) diff --git a/community/www/courses/about/index.html b/community/www/courses/about/index.html index 99eb138f..46bf0e13 100644 --- a/community/www/courses/about/index.html +++ b/community/www/courses/about/index.html @@ -6,6 +6,8 @@ {% block head_include %} + + {% endblock %} {% block content %} diff --git a/community/www/courses/discuss/index.html b/community/www/courses/discuss/index.html index b4e19f86..73270d8e 100644 --- a/community/www/courses/discuss/index.html +++ b/community/www/courses/discuss/index.html @@ -6,6 +6,8 @@ {% block head_include %} + + {% endblock %} {% block content %} diff --git a/community/www/courses/learn/index.html b/community/www/courses/learn/index.html index 24cf5504..db3a4549 100644 --- a/community/www/courses/learn/index.html +++ b/community/www/courses/learn/index.html @@ -9,6 +9,7 @@ + diff --git a/community/www/courses/members/index.html b/community/www/courses/members/index.html index 618ce2ac..bc7ce6f1 100644 --- a/community/www/courses/members/index.html +++ b/community/www/courses/members/index.html @@ -7,6 +7,7 @@ {% block head_include %} + {% endblock %} {% block content %} diff --git a/community/www/courses/schedule/index.html b/community/www/courses/schedule/index.html index 8f3cff12..f78a9397 100644 --- a/community/www/courses/schedule/index.html +++ b/community/www/courses/schedule/index.html @@ -4,6 +4,7 @@ {% block head_include %} + {% endblock %} {% block content %} diff --git a/community/www/macros/sidebar.html b/community/www/macros/sidebar.html index d0922924..c36ab8ee 100644 --- a/community/www/macros/sidebar.html +++ b/community/www/macros/sidebar.html @@ -1,12 +1,12 @@ {% macro Sidebar(course, batch_code) %} -{% endmacro %} \ No newline at end of file +{% endmacro %} From 9cb9fad05c14fbeec64d2b66af0ee8abb7806ee9 Mon Sep 17 00:00:00 2001 From: Anand Chitipothu Date: Thu, 20 May 2021 16:24:41 +0530 Subject: [PATCH 9/9] fix: fixed failing tests --- community/lms/doctype/exercise/test_exercise.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/community/lms/doctype/exercise/test_exercise.py b/community/lms/doctype/exercise/test_exercise.py index 02dc2c38..8ae68e96 100644 --- a/community/lms/doctype/exercise/test_exercise.py +++ b/community/lms/doctype/exercise/test_exercise.py @@ -8,11 +8,19 @@ class TestExercise(unittest.TestCase): def setUp(self): frappe.db.sql('delete from `tabExercise Submission`') frappe.db.sql('delete from `tabExercise`') + frappe.db.sql('delete from `tabLMS Course`') def new_exercise(self): + course = frappe.get_doc({ + "doctype": "LMS Course", + "name": "test-course", + "title": "Test Course" + }) + course.insert() e = frappe.get_doc({ "doctype": "Exercise", "name": "test-problem", + "course": course.name, "title": "Test Problem", "description": "draw a circle", "code": "# draw a single cicle", @@ -31,6 +39,8 @@ class TestExercise(unittest.TestCase): e = self.new_exercise() submission = e.submit("circle(100, 100, 50)") assert submission is not None + assert submission.exercise == e.name + assert submission.course == e.course user_submission = e.get_user_submission() assert user_submission is not None