From b9a93bb1602d88275071ad5fc9ff69d10612e389 Mon Sep 17 00:00:00 2001 From: Anand Chitipothu Date: Tue, 1 Jun 2021 05:46:32 +0530 Subject: [PATCH 1/4] feat: added actions to reindex lessons and exercises Some lessons gets deleted and some new ones get added in the progress of course creation and it may happen then some of the lesson index may become inconsistent. Also, we would like to maintain an index for the exercises. To support both of these, added actions to reindex lessons and exercises to the course doctype. --- community/lms/doctype/chapter/chapter.py | 4 +- community/lms/doctype/exercise/exercise.json | 25 ++++++++-- community/lms/doctype/exercise/exercise.py | 1 - community/lms/doctype/lesson/lesson.json | 10 +++- community/lms/doctype/lesson/lesson.py | 3 ++ .../lms/doctype/lms_course/lms_course.json | 17 ++++++- .../lms/doctype/lms_course/lms_course.py | 47 ++++++++++++++++++- 7 files changed, 96 insertions(+), 11 deletions(-) diff --git a/community/lms/doctype/chapter/chapter.py b/community/lms/doctype/chapter/chapter.py index fb201a4c..b616de2c 100644 --- a/community/lms/doctype/chapter/chapter.py +++ b/community/lms/doctype/chapter/chapter.py @@ -10,6 +10,6 @@ class Chapter(Document): def get_lessons(self): rows = frappe.db.get_all("Lesson", filters={"chapter": self.name}, - fields='*', + fields='name', order_by="index_") - return [frappe.get_doc(dict(row, doctype='Lesson')) for row in rows] + return [frappe.get_doc('Lesson', row['name']) for row in rows] diff --git a/community/lms/doctype/exercise/exercise.json b/community/lms/doctype/exercise/exercise.json index 33c7151e..e7f0ea7c 100644 --- a/community/lms/doctype/exercise/exercise.json +++ b/community/lms/doctype/exercise/exercise.json @@ -15,7 +15,9 @@ "hints", "tests", "image", - "lesson" + "lesson", + "index_", + "index_label" ], "fields": [ { @@ -27,6 +29,7 @@ { "fieldname": "course", "fieldtype": "Link", + "in_list_view": 1, "label": "Course", "options": "LMS Course" }, @@ -73,13 +76,27 @@ { "fieldname": "lesson", "fieldtype": "Link", + "in_list_view": 1, "label": "Lesson", "options": "Lesson" + }, + { + "fieldname": "index_", + "fieldtype": "Int", + "label": "Index", + "read_only": 1 + }, + { + "fieldname": "index_label", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Index Label", + "read_only": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-05-20 13:23:12.340928", + "modified": "2021-06-01 05:22:15.656013", "modified_by": "Administrator", "module": "LMS", "name": "Exercise", @@ -99,8 +116,8 @@ } ], "search_fields": "title", - "sort_field": "modified", - "sort_order": "DESC", + "sort_field": "index_label", + "sort_order": "ASC", "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 index 151b993a..dde6b273 100644 --- a/community/lms/doctype/exercise/exercise.py +++ b/community/lms/doctype/exercise/exercise.py @@ -25,7 +25,6 @@ class Exercise(Document): order_by="creation desc", page_length=1) - print("get_user_submission", result) if result: return result[0] diff --git a/community/lms/doctype/lesson/lesson.json b/community/lms/doctype/lesson/lesson.json index 2dbe6c92..4772b255 100644 --- a/community/lms/doctype/lesson/lesson.json +++ b/community/lms/doctype/lesson/lesson.json @@ -10,6 +10,7 @@ "lesson_type", "title", "index_", + "index_label", "body", "sections" ], @@ -51,11 +52,18 @@ "fieldtype": "Table", "label": "Sections", "options": "LMS Section" + }, + { + "fieldname": "index_label", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Index Label", + "read_only": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-05-13 20:03:51.510605", + "modified": "2021-06-01 05:30:48.127494", "modified_by": "Administrator", "module": "LMS", "name": "Lesson", diff --git a/community/lms/doctype/lesson/lesson.py b/community/lms/doctype/lesson/lesson.py index 979515ea..abed257a 100644 --- a/community/lms/doctype/lesson/lesson.py +++ b/community/lms/doctype/lesson/lesson.py @@ -20,6 +20,9 @@ class Lesson(Document): def get_sections(self): return sorted(self.get('sections'), key=lambda s: s.index) + def get_exercises(self): + return [frappe.get_doc("Exercise", s.id) for s in self.get("sections") if s.type=="exercise"] + def make_lms_section(self, index, section): s = frappe.new_doc('LMS Section', parent_doc=self, parentfield='sections') s.type = section.type diff --git a/community/lms/doctype/lms_course/lms_course.json b/community/lms/doctype/lms_course/lms_course.json index 41f4de73..187a24e1 100644 --- a/community/lms/doctype/lms_course/lms_course.json +++ b/community/lms/doctype/lms_course/lms_course.json @@ -1,5 +1,18 @@ { - "actions": [], + "actions": [ + { + "action": "community.lms.doctype.lms_course.lms_course.reindex_lessons", + "action_type": "Server Action", + "group": "Reindex", + "label": "Reindex Lessons" + }, + { + "action": "community.lms.doctype.lms_course.lms_course.reindex_exercises", + "action_type": "Server Action", + "group": "Reindex", + "label": "Reindex Exercises" + } + ], "allow_guest_to_view": 1, "allow_rename": 1, "creation": "2021-03-01 16:49:33.622422", @@ -86,7 +99,7 @@ "link_fieldname": "course" } ], - "modified": "2021-05-23 18:14:32.602647", + "modified": "2021-06-01 04:36:45.696776", "modified_by": "Administrator", "module": "LMS", "name": "LMS Course", diff --git a/community/lms/doctype/lms_course/lms_course.py b/community/lms/doctype/lms_course/lms_course.py index f72264ba..b3d08ef9 100644 --- a/community/lms/doctype/lms_course/lms_course.py +++ b/community/lms/doctype/lms_course/lms_course.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document +import json from ...utils import slugify from community.query import find, find_all @@ -157,6 +158,35 @@ class LMSCourse(Document): chapter = frappe.get_doc("Chapter", lesson.chapter) return f"{chapter.index_}.{lesson.index_}" + def reindex_lessons(self): + for i, c in enumerate(self.get_chapters(), start=1): + c.index_ = i + c.save() + self._reindex_lessons_in_chapter(c) + + def _reindex_lessons_in_chapter(self, c): + for i, lesson in enumerate(c.get_lessons(), start=1): + lesson.index = i + lesson.index_label = f"{c.index_}.{i}" + lesson.save() + + def reindex_exercises(self): + for i, c in enumerate(self.get_chapters(), start=1): + if c.index_ != i: + c.index_ = i + c.save() + self._reindex_exercises_in_chapter(c) + + def _reindex_exercises_in_chapter(self, c): + i = 1 + for lesson in c.get_lessons(): + for exercise in lesson.get_exercises(): + exercise.index_ = i + exercise.index_label = f"{c.index_}.{i}" + exercise.save() + i += 1 + + def get_outline(self): return CourseOutline(self) @@ -187,7 +217,8 @@ class CourseOutline: def get_chapters(self): return frappe.db.get_all("Chapter", filters={"course": self.course.name}, - fields=["name", "title", "index_"]) + fields=["name", "title", "index_"], + order_by="index_") def get_lessons(self): chapters = [c['name'] for c in self.chapters] @@ -199,3 +230,17 @@ class CourseOutline: for lesson in lessons: lesson['number'] = "{}.{}".format(chapter_numbers[lesson['chapter']], lesson['index_']) return lessons + +@frappe.whitelist() +def reindex_lessons(doc): + course_data = json.loads(doc) + course = frappe.get_doc("LMS Course", course_data['name']) + course.reindex_lessons() + frappe.msgprint("All lessons in this course have been re-indexed.") + +@frappe.whitelist() +def reindex_exercises(doc): + course_data = json.loads(doc) + course = frappe.get_doc("LMS Course", course_data['name']) + course.reindex_exercises() + frappe.msgprint("All exercises in this course have been re-indexed.") From a12a52747edb32e87572cacc4630bb9b5b22be86 Mon Sep 17 00:00:00 2001 From: Anand Chitipothu Date: Tue, 1 Jun 2021 05:49:45 +0530 Subject: [PATCH 2/4] feat: show exercise index in the title Show exercise as "Exercise 2.1: Draw a Circle". --- community/lms/widgets/Exercise.html | 2 +- community/www/batch/progress.html | 2 +- community/www/batch/progress.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/community/lms/widgets/Exercise.html b/community/lms/widgets/Exercise.html index 1dbaecfa..04361609 100644 --- a/community/lms/widgets/Exercise.html +++ b/community/lms/widgets/Exercise.html @@ -1,7 +1,7 @@ {% from "www/macros/livecode.html" import LiveCodeEditorJS, LiveCodeEditor with context %}
-

{{ exercise.title }}

+

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

{{frappe.utils.md_to_html(exercise.description)}}
{% if exercise.image %} diff --git a/community/www/batch/progress.html b/community/www/batch/progress.html index 2083f046..983077b1 100644 --- a/community/www/batch/progress.html +++ b/community/www/batch/progress.html @@ -29,7 +29,7 @@

Batch Progress

{% for exercise in report.exercises %}
-

{{exercise.title}}

+

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

{% for s in report.get_submissions_of_exercise(exercise.name) %}

{{s.owner.full_name}}

diff --git a/community/www/batch/progress.py b/community/www/batch/progress.py index 5556d03d..af22ede5 100644 --- a/community/www/batch/progress.py +++ b/community/www/batch/progress.py @@ -25,7 +25,7 @@ class BatchReport: self.submissions_by_exercise[s.exercise].append(s) def get_exercises(self, course_name): - return frappe.get_all("Exercise", {"course": course_name}, ["name", "title"]) + return frappe.get_all("Exercise", {"course": course_name}, ["name", "title", "index_label"], order_by="index_label") def get_submissions_of_exercise(self, exercise_name): return self.submissions_by_exercise[exercise_name] From 400e706be1d58bb87ee1088556ca236ae433b3c2 Mon Sep 17 00:00:00 2001 From: Anand Chitipothu Date: Tue, 1 Jun 2021 05:59:01 +0530 Subject: [PATCH 3/4] feat: update the index of orphan exercises When an exercise is removed from a lesson, the link to the lesson is removed from that exercise and the index is reset. This will make sure the removed exercises won't show up in places like progress. --- community/lms/doctype/lesson/lesson.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/community/lms/doctype/lesson/lesson.py b/community/lms/doctype/lesson/lesson.py index abed257a..3358dbe0 100644 --- a/community/lms/doctype/lesson/lesson.py +++ b/community/lms/doctype/lesson/lesson.py @@ -16,6 +16,21 @@ class Lesson(Document): e = s.get_exercise() e.lesson = self.name e.save() + self.update_orphan_exercises() + + def update_orphan_exercises(self): + """Updates the exercises that were previously part of this lesson, + but not any more. + """ + linked_exercises = {row['name'] for row in frappe.get_all('Exercise', {"lesson": self.name})} + active_exercises = {s.id for s in self.get("sections") if s.type=="exercise"} + orphan_exercises = linked_exercises - active_exercises + for name in orphan_exercises: + ex = frappe.get_doc("Exercise", name) + ex.lesson = None + ex.index_ = 0 + ex.index_label = "" + ex.save() def get_sections(self): return sorted(self.get('sections'), key=lambda s: s.index) From c96a14c972a0b8cdb892694df2a6251ed641e9f9 Mon Sep 17 00:00:00 2001 From: Anand Chitipothu Date: Tue, 1 Jun 2021 08:15:52 +0530 Subject: [PATCH 4/4] feat: ignore orphan exercises in the progress Don't show exercises that are not added to any lesson in the progress. --- community/www/batch/progress.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/community/www/batch/progress.py b/community/www/batch/progress.py index af22ede5..adfb72dd 100644 --- a/community/www/batch/progress.py +++ b/community/www/batch/progress.py @@ -25,7 +25,7 @@ class BatchReport: self.submissions_by_exercise[s.exercise].append(s) def get_exercises(self, course_name): - return frappe.get_all("Exercise", {"course": course_name}, ["name", "title", "index_label"], order_by="index_label") + return frappe.get_all("Exercise", {"course": course_name, "lesson": ["!=", ""]}, ["name", "title", "index_label"], order_by="index_label") def get_submissions_of_exercise(self, exercise_name): return self.submissions_by_exercise[exercise_name]