From 3e46db9c1163af864c186ee5aba8b913d83d7d9a Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Mon, 7 Feb 2022 12:01:07 +0530 Subject: [PATCH 1/7] feat: multiple instructors --- .../lms/doctype/course_instructor/__init__.py | 0 .../course_instructor/course_instructor.json | 32 +++++++++++++++++++ .../course_instructor/course_instructor.py | 8 +++++ school/lms/doctype/lms_course/lms_course.json | 8 ++--- school/patches.txt | 1 + school/patches/v0_0/multiple_instructors.py | 4 +++ 6 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 school/lms/doctype/course_instructor/__init__.py create mode 100644 school/lms/doctype/course_instructor/course_instructor.json create mode 100644 school/lms/doctype/course_instructor/course_instructor.py create mode 100644 school/patches/v0_0/multiple_instructors.py diff --git a/school/lms/doctype/course_instructor/__init__.py b/school/lms/doctype/course_instructor/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/school/lms/doctype/course_instructor/course_instructor.json b/school/lms/doctype/course_instructor/course_instructor.json new file mode 100644 index 00000000..3ef8630a --- /dev/null +++ b/school/lms/doctype/course_instructor/course_instructor.json @@ -0,0 +1,32 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2022-02-07 11:39:59.998762", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "instructor" + ], + "fields": [ + { + "fieldname": "instructor", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Instructor", + "options": "User" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2022-02-07 11:41:42.943250", + "modified_by": "Administrator", + "module": "LMS", + "name": "Course Instructor", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/school/lms/doctype/course_instructor/course_instructor.py b/school/lms/doctype/course_instructor/course_instructor.py new file mode 100644 index 00000000..d229178c --- /dev/null +++ b/school/lms/doctype/course_instructor/course_instructor.py @@ -0,0 +1,8 @@ +# Copyright (c) 2022, Frappe and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class CourseInstructor(Document): + pass diff --git a/school/lms/doctype/lms_course/lms_course.json b/school/lms/doctype/lms_course/lms_course.json index 86b7e5d1..2a4b0c25 100644 --- a/school/lms/doctype/lms_course/lms_course.json +++ b/school/lms/doctype/lms_course/lms_course.json @@ -105,11 +105,10 @@ }, { "fieldname": "instructor", - "fieldtype": "Link", - "in_list_view": 1, + "fieldtype": "Table MultiSelect", "in_standard_filter": 1, "label": "Instructor", - "options": "User" + "options": "Course Instructor" }, { "fieldname": "section_break_7", @@ -168,7 +167,7 @@ "link_fieldname": "course" } ], - "modified": "2021-09-30 10:36:48.759995", + "modified": "2022-02-07 11:41:39.735324", "modified_by": "Administrator", "module": "LMS", "name": "LMS Course", @@ -190,6 +189,7 @@ "search_fields": "title", "sort_field": "creation", "sort_order": "DESC", + "states": [], "title_field": "title", "track_changes": 1 } \ No newline at end of file diff --git a/school/patches.txt b/school/patches.txt index 4ce65a63..c7c9cce3 100644 --- a/school/patches.txt +++ b/school/patches.txt @@ -21,3 +21,4 @@ execute:frappe.delete_doc("DocType", "LMS Topic") #06-10-2021 school.patches.v0_0.add_progress_to_membership #20-10-2021 execute:frappe.delete_doc("Workspace", "LMS", ignore_missing=True, force=True) #24-10-2021 execute:frappe.delete_doc("Custom Field", "User-verify_age", ignore_missing=True, force=True) +school.patches.v0_0.multiple_instructors diff --git a/school/patches/v0_0/multiple_instructors.py b/school/patches/v0_0/multiple_instructors.py new file mode 100644 index 00000000..1da27ace --- /dev/null +++ b/school/patches/v0_0/multiple_instructors.py @@ -0,0 +1,4 @@ +import frappe + +def execute(): + frappe.get_all("LMS Course", fields=["name", "instructor"]) From bf3a496ea3b5dbc346f89c2187d7b496ba1d1959 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Mon, 7 Feb 2022 13:41:28 +0530 Subject: [PATCH 2/7] feat: multiple instructors on course card and course home --- school/lms/doctype/lms_course/lms_course.json | 10 +++++----- school/lms/doctype/lms_course/lms_course.py | 12 ++++++++---- school/lms/widgets/CourseCard.html | 8 +++++--- school/lms/widgets/CourseTeaser.html | 19 ------------------- school/patches.txt | 2 +- school/patches/v0_0/multiple_instructors.py | 12 +++++++++++- school/www/courses/course.html | 4 +++- 7 files changed, 33 insertions(+), 34 deletions(-) delete mode 100644 school/lms/widgets/CourseTeaser.html diff --git a/school/lms/doctype/lms_course/lms_course.json b/school/lms/doctype/lms_course/lms_course.json index 2a4b0c25..5b63e616 100644 --- a/school/lms/doctype/lms_course/lms_course.json +++ b/school/lms/doctype/lms_course/lms_course.json @@ -19,7 +19,7 @@ "video_link", "image", "column_break_3", - "instructor", + "instructors", "tags", "section_break_7", "is_published", @@ -104,10 +104,10 @@ "options": "Chapter Reference" }, { - "fieldname": "instructor", + "fieldname": "instructors", "fieldtype": "Table MultiSelect", "in_standard_filter": 1, - "label": "Instructor", + "label": "Instructors", "options": "Course Instructor" }, { @@ -167,7 +167,7 @@ "link_fieldname": "course" } ], - "modified": "2022-02-07 11:41:39.735324", + "modified": "2022-02-07 11:41:39.735325", "modified_by": "Administrator", "module": "LMS", "name": "LMS Course", @@ -192,4 +192,4 @@ "states": [], "title_field": "title", "track_changes": 1 -} \ No newline at end of file +} diff --git a/school/lms/doctype/lms_course/lms_course.py b/school/lms/doctype/lms_course/lms_course.py index 31ed35e0..91137cf9 100644 --- a/school/lms/doctype/lms_course/lms_course.py +++ b/school/lms/doctype/lms_course/lms_course.py @@ -143,10 +143,14 @@ class LMSCourse(Document): fieldname="batch") return batch_name and frappe.get_doc("LMS Batch", batch_name) - def get_instructor(self): - if self.instructor: - return frappe.get_doc("User", self.instructor) - return frappe.get_doc("User", self.owner) + def get_instructors(self): + instructors = [] + if self.instructors: + for instructor in self.instructors: + instructors.append(frappe.get_doc("User", instructor.instructor)) + else: + instructors.append(frappe.get_doc("User", self.owner)) + return instructors def get_chapters(self): """Returns all chapters of this course. diff --git a/school/lms/widgets/CourseCard.html b/school/lms/widgets/CourseCard.html index b639f11d..e5b51737 100644 --- a/school/lms/widgets/CourseCard.html +++ b/school/lms/widgets/CourseCard.html @@ -41,14 +41,16 @@
{{ course.title }}
+ {% for instructor in course.get_instructors() %} - {{ widgets.Avatar(member=course.get_instructor(), avatar_class="avatar-small") }} - + {{ widgets.Avatar(member=instructor, avatar_class="avatar-small") }} + - {{ course.get_instructor().full_name }} + {{ instructor.full_name }} + {% endfor %} {% if course.get_students() | length %} diff --git a/school/lms/widgets/CourseTeaser.html b/school/lms/widgets/CourseTeaser.html deleted file mode 100644 index 280d2783..00000000 --- a/school/lms/widgets/CourseTeaser.html +++ /dev/null @@ -1,19 +0,0 @@ -
-
-

{{ course.title }}

-
- {{ course.short_introduction or "" }} -
-
- -
diff --git a/school/patches.txt b/school/patches.txt index c7c9cce3..0a1c636f 100644 --- a/school/patches.txt +++ b/school/patches.txt @@ -21,4 +21,4 @@ execute:frappe.delete_doc("DocType", "LMS Topic") #06-10-2021 school.patches.v0_0.add_progress_to_membership #20-10-2021 execute:frappe.delete_doc("Workspace", "LMS", ignore_missing=True, force=True) #24-10-2021 execute:frappe.delete_doc("Custom Field", "User-verify_age", ignore_missing=True, force=True) -school.patches.v0_0.multiple_instructors +school.patches.v0_0.multiple_instructors #08-02-2022 diff --git a/school/patches/v0_0/multiple_instructors.py b/school/patches/v0_0/multiple_instructors.py index 1da27ace..8d914d7c 100644 --- a/school/patches/v0_0/multiple_instructors.py +++ b/school/patches/v0_0/multiple_instructors.py @@ -1,4 +1,14 @@ import frappe def execute(): - frappe.get_all("LMS Course", fields=["name", "instructor"]) + frappe.reload_doc("lms", "doctype", "lms_course") + courses = frappe.get_all("LMS Course", fields=["name", "instructor"]) + for course in courses: + doc = frappe.get_doc({ + "doctype": "Course Instructor", + "parent": course.name, + "parentfield": "instructors", + "parenttype": "LMS Course", + "instructor": course.instructor + }) + doc.save() diff --git a/school/www/courses/course.html b/school/www/courses/course.html index 566da4bc..38a5609d 100644 --- a/school/www/courses/course.html +++ b/school/www/courses/course.html @@ -129,7 +129,9 @@
Creator
- {{ widgets.MemberCard(member=course.get_instructor(), show_course_count=True, avatar_class="avatar-large") }} + {% for instructor in course.get_instructors() %} + {{ widgets.MemberCard(member=instructor, show_course_count=True, avatar_class="avatar-large") }} + {% endfor %}
{% endmacro %} From 57c69a7d6c9805a754660f25ac2da35cea435f2c Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Tue, 8 Feb 2022 16:13:38 +0530 Subject: [PATCH 3/7] fix: removing get_doc dependencies for lms course --- school/community/widgets/Avatar.html | 2 +- school/hooks.py | 6 +- school/lms/doctype/lms_course/lms_course.py | 71 +------------- .../lms_course_review/lms_course_review.py | 2 +- school/lms/md.py | 2 +- school/lms/widgets/CourseCard.html | 76 +++++---------- school/overrides/user.py | 42 ++++---- school/public/css/style.css | 57 ++++++----- school/www/batch/utils.py | 35 ------- school/www/courses/index.html | 4 +- school/www/courses/index.py | 12 +-- school/www/utils.py | 97 +++++++++++++++++++ 12 files changed, 193 insertions(+), 213 deletions(-) delete mode 100644 school/www/batch/utils.py create mode 100644 school/www/utils.py diff --git a/school/community/widgets/Avatar.html b/school/community/widgets/Avatar.html index 6450ac9f..47cf60c9 100644 --- a/school/community/widgets/Avatar.html +++ b/school/community/widgets/Avatar.html @@ -1,4 +1,4 @@ -{% set color = member.get_palette() %} +{% set color = get_palette(member.full_name) %} {% if member.user_image %} diff --git a/school/hooks.py b/school/hooks.py index 5f305c20..3af95593 100644 --- a/school/hooks.py +++ b/school/hooks.py @@ -161,7 +161,11 @@ update_website_context = [ jinja = { "methods": [ - "school.page_renderers.get_profile_url" + "school.page_renderers.get_profile_url", + "school.overrides.user.get_palette", + "school.www.utils.get_membership", + "school.www.utils.get_lessons", + "school.www.utils.get_tags" ], "filters": [] } diff --git a/school/lms/doctype/lms_course/lms_course.py b/school/lms/doctype/lms_course/lms_course.py index 31ed35e0..b0c4c4e2 100644 --- a/school/lms/doctype/lms_course/lms_course.py +++ b/school/lms/doctype/lms_course/lms_course.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2021, FOSS United and contributors +# Copyright (c) 2021, Frappe and contributors # For license information, please see license.txt from __future__ import unicode_literals @@ -143,46 +142,7 @@ class LMSCourse(Document): fieldname="batch") return batch_name and frappe.get_doc("LMS Batch", batch_name) - def get_instructor(self): - if self.instructor: - return frappe.get_doc("User", self.instructor) - return frappe.get_doc("User", self.owner) - - def get_chapters(self): - """Returns all chapters of this course. - """ - chapters = [] - for row in self.chapters: - chapter_details = frappe.db.get_value("Course Chapter", row.chapter, - ["name", "title", "description"], - as_dict=True) - chapter_details.idx = row.idx - chapters.append(chapter_details) - return chapters - - def get_lessons(self, chapter=None): - """ If chapter is passed, returns lessons of only that chapter. - Else returns lessons of all chapters of the course """ - lessons = [] - - if chapter: - return self.get_lesson_details(chapter) - - for chapter in self.get_chapters(): - lesson = self.get_lesson_details(chapter) - lessons += lesson - - return lessons - - def get_lesson_details(self, chapter): - lessons = [] - lesson_list = frappe.get_all("Lesson Reference", {"parent": chapter.name}, - ["lesson", "idx"], order_by="idx") - for row in lesson_list: - lesson_details = frappe.get_doc("Course Lesson", row.lesson) - lesson_details.number = flt("{}.{}".format(chapter.idx, row.idx)) - lessons.append(lesson_details) - return lessons + def get_slugified_chapter_title(self, chapter): return slugify(chapter) @@ -201,15 +161,6 @@ class LMSCourse(Document): batch_names = {m.batch for m in memberships} return [b for b in batches if b.name in batch_names] - def get_upcoming_batches(self): - now = frappe.utils.nowdate() - batches = find_all("LMS Batch", - course=self.name, - start_date=[">", now], - status="Active", - visibility="Public") - return batches - def get_cohorts(self): return find_all("Cohort", course=self.name, order_by="creation") @@ -263,22 +214,7 @@ class LMSCourse(Document): return return f"/courses/{self.name}/learn/{lesson_number}" - def get_membership(self, member, batch=None): - filters = { - "member": member, - "course": self.name - } - if batch: - filters["batch"] = batch - membership = frappe.db.get_value("LMS Batch Membership", - filters, - ["name", "batch", "current_lesson", "member_type", "progress"], - 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): all_memberships = frappe.get_all("LMS Batch Membership", {"member": member, "course": self.name}, ["batch"]) @@ -302,8 +238,7 @@ class LMSCourse(Document): member_names = [m['member'] for m in memberships] return find_all("User", name=["IN", member_names]) - def get_tags(self): - return self.tags.split(",") if self.tags else [] + def get_reviews(self): reviews = frappe.get_all("LMS Course Review", diff --git a/school/lms/doctype/lms_course_review/lms_course_review.py b/school/lms/doctype/lms_course_review/lms_course_review.py index ffe100b9..8e274450 100644 --- a/school/lms/doctype/lms_course_review/lms_course_review.py +++ b/school/lms/doctype/lms_course_review/lms_course_review.py @@ -1,4 +1,4 @@ -# Copyright (c) 2021, FOSS United and contributors +# Copyright (c) 2021, Frappe and contributors # For license information, please see license.txt import frappe diff --git a/school/lms/md.py b/school/lms/md.py index 5acb6322..b072af2a 100644 --- a/school/lms/md.py +++ b/school/lms/md.py @@ -105,7 +105,7 @@ def sanitize_html(html, macro): any broken tags. This makes sures that all those things are fixed before passing to the etree parser. """ - soup = BeautifulSoup(html, features="lxml") + soup = BeautifulSoup(html, features="html5lib") nodes = soup.body.children classname = "" if macro == "YouTubeVideo": diff --git a/school/lms/widgets/CourseCard.html b/school/lms/widgets/CourseCard.html index b639f11d..443a6d54 100644 --- a/school/lms/widgets/CourseCard.html +++ b/school/lms/widgets/CourseCard.html @@ -1,47 +1,41 @@ -{% set membership = course.get_membership(frappe.session.user) %} +{% set membership = get_membership(course.name, frappe.session.user) %} {% set progress = frappe.utils.cint(membership.progress) %}
- {% for tag in course.get_tags() %} + {% for tag in get_tags(course.name) %}
{{ tag }}
{% endfor %} - {% if membership and not read_only %} - {% if progress < 100 %}
{{ frappe.utils.rounded(progress) }}% - {{ _("Completed") }}
- {% else %} -
{{ _("Completed") - }}
+ {% if not course.image %} +
{{ course.title[0] }}
{% endif %} - {% endif %} -
- {% if not course.image %} -
{{ course.title[0] }}
- {% endif %}
-
- {% if course.get_chapters() | length %} +
+ {% if get_lessons(course.name) | length %} - {{ course.get_chapters() | length }} {{ _("Chapters") }} - - {% endif %} - {% if course.get_chapters() | length and course.get_upcoming_batches() | length %} - . - {% endif %} - {% if course.get_upcoming_batches() | length %} - - {{ course.get_upcoming_batches() | length }} {{ _("Open Batches") }} + {{ get_lessons(course.name) | length }} {{ _("Lessons") }} {% endif %}
{{ course.title }}
-
- + + {% if membership and not read_only %} +
+
+ {{ progress }} Complete +
+
+
{{ progress }}% Completed
+ {% endif %} + +