From 8a242a69fbaaa6f296656c9d05d346dc9a459251 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Wed, 5 May 2021 15:45:20 +0530 Subject: [PATCH 01/15] fix: mentor request flow and emails #30, #69, #70 --- .../lms/doctype/lms_course/lms_course.json | 7 +- .../lms_mentor_request/lms_mentor_request.py | 172 +++++++++++------- community/patches.txt | 3 +- .../create_mentor_request_email_templates.py | 31 ++++ .../emails/mentor_request_creation_email.html | 9 + .../mentor_request_status_update_email.html | 8 + community/www/courses/course.js | 2 +- 7 files changed, 159 insertions(+), 73 deletions(-) create mode 100644 community/patches/create_mentor_request_email_templates.py create mode 100644 community/templates/emails/mentor_request_creation_email.html create mode 100644 community/templates/emails/mentor_request_status_update_email.html diff --git a/community/lms/doctype/lms_course/lms_course.json b/community/lms/doctype/lms_course/lms_course.json index 0c104240..cad65d29 100644 --- a/community/lms/doctype/lms_course/lms_course.json +++ b/community/lms/doctype/lms_course/lms_course.json @@ -87,9 +87,14 @@ "group": "Mentors", "link_doctype": "LMS Course Mentor Mapping", "link_fieldname": "course" + }, + { + "group": "Mentors", + "link_doctype": "LMS Mentor Request", + "link_fieldname": "course" } ], - "modified": "2021-05-03 05:52:30.396824", + "modified": "2021-05-05 14:17:26.297602", "modified_by": "Administrator", "module": "LMS", "name": "LMS Course", diff --git a/community/lms/doctype/lms_mentor_request/lms_mentor_request.py b/community/lms/doctype/lms_mentor_request/lms_mentor_request.py index b98a2f45..d8b7ca6a 100644 --- a/community/lms/doctype/lms_mentor_request/lms_mentor_request.py +++ b/community/lms/doctype/lms_mentor_request/lms_mentor_request.py @@ -8,86 +8,118 @@ from frappe.model.document import Document from frappe import _ class LMSMentorRequest(Document): - def on_update(self): - if self.has_value_changed('status'): - template = frappe.db.get_single_value('LMS Settings', 'mentor_request_status_update') - if not template: - return + def on_update(self): + if self.has_value_changed('status'): - email_template = frappe.get_doc('Email Template', template) - message = frappe.render_template(email_template.response, {'member_name': self.member_name, 'status': self.status}) - subject = _('The status of your application has changed.') - member_email = frappe.db.get_value("Community Member", self.member, "email") - - if self.status == 'Approved' or self.status == 'Rejected': - reviewed_by = frappe.db.get_value('Community Member', self.reviewed_by, 'email') - send_email(member_email, [get_course_author(self.course), reviewed_by], subject, message) - - elif self.status == 'Withdrawn': - send_email([member_email, get_course_author(self.course)], None, subject, message) + if self.status == "Approved": + self.create_course_mentor_mapping() + + if self.status != "Pending": + self.send_status_change_email() + + def create_course_mentor_mapping(self): + mapping = frappe.get_doc({ + "doctype": "LMS Course Mentor Mapping", + "mentor": self.member, + "course": self.course + }) + mapping.save() + + def send_creation_email(self, member): + email_template = self.get_email_template('mentor_request_creation') + if not email_template: + return + + course_details = frappe.db.get_value("LMS Course", self.course, ["owner", "slug"], as_dict=True) + message = frappe.render_template(email_template.response, + { + 'member_name': member.full_name, + 'course_url': '/courses/' + course_details.slug + }) + + email_args = { + "recipients": [frappe.session.user, course_details.owner], + "subject": email_template.subject, + "header": email_template.subject, + "message": message + } + frappe.enqueue(method=frappe.sendmail, queue="short", timeout=300, is_async=True, **email_args) + + def send_status_change_email(self): + email_template = self.get_email_template('mentor_request_status_update') + if not email_template: + return + + message = frappe.render_template(email_template.response, + { + 'member_name': self.member_name, + 'status': self.status + }) + + member_email = frappe.db.get_value("Community Member", self.member, "email") + course_details = frappe.db.get_value("LMS Course", self.course, ["owner"], as_dict=True) + + if self.status == 'Approved' or self.status == 'Rejected': + reviewed_by = frappe.db.get_value('Community Member', self.reviewed_by, 'email') + email_args = { + "recipients": member_email, + "cc": [course_details.owner, reviewed_by], + "subject": email_template.subject, + "header": email_template.subject, + "message": message + } + frappe.enqueue(method=frappe.sendmail, queue="short", timeout=300, is_async=True, **email_args) + + elif self.status == 'Withdrawn': + email_args = { + "recipients": [member_email, course_details.owner], + "subject": email_template.subject, + "header": email_template.subject, + "message": message + } + frappe.enqueue(method=frappe.sendmail, queue="short", timeout=300, is_async=True, **email_args) + + def get_email_template(self, template_name): + template = frappe.db.get_single_value('LMS Settings', template_name) + if template: + return frappe.get_doc('Email Template', template) @frappe.whitelist() def has_requested(course): - return len(frappe.get_all('LMS Mentor Request', - filters = { - 'member': get_member().name, - 'course': course, - 'status': ['in', ('Pending', 'Approved')] - } - ) + return frappe.db.count('LMS Mentor Request', + filters = { + 'member': get_member().name, + 'course': course, + 'status': ['in', ('Pending', 'Approved')] + } ) @frappe.whitelist() def create_request(course): - if not has_requested(course): - member = get_member() - frappe.get_doc({ - 'doctype': 'LMS Mentor Request', - 'member': member.name, - 'course': course, - 'status': 'Pending' - }).save(ignore_permissions=True) - send_creation_email(course, member) - return 'OK' - else: - return 'Already Applied' + if not has_requested(course): + member = get_member() + request = frappe.get_doc({ + 'doctype': 'LMS Mentor Request', + 'member': member.name, + 'course': course, + 'status': 'Pending' + }) + request.save(ignore_permissions=True) + request.send_creation_email(member) + return 'OK' + + else: + return 'Already Applied' @frappe.whitelist() def cancel_request(course): - request = frappe.get_doc('LMS Mentor Request', {'member': get_member().name, 'course': course, 'status': ['in', ('Pending', 'Approved')]}) - request.status = 'Withdrawn' - request.save(ignore_permissions=True) - return 'OK' + request = frappe.get_doc('LMS Mentor Request', {'member': get_member().name, 'course': course, 'status': ['in', ('Pending', 'Approved')]}) + request.status = 'Withdrawn' + request.save(ignore_permissions=True) + return 'OK' def get_member(): - try: - return frappe.get_doc('Community Member', {'email': frappe.session.user}) - except frappe.DoesNotExistError: - return - -def get_course_author(course): - return frappe.db.get_value('LMS Course', course, 'owner') - -def send_creation_email(course, member): - template = frappe.db.get_single_value('LMS Settings', 'mentor_request_creation') - if not template: - return - - email_template = frappe.get_doc('Email Template', template) - member_name = member.full_name - message = frappe.render_template(email_template.response, {'member_name': member_name}) - subject = _('Request for Mentorship') - send_email([frappe.session.user, get_course_author(course)], None, subject, message) - -def send_email(recipients, cc=None, subject=None, message=None, template=None, args=None): - frappe.sendmail( - recipients = recipients, - cc = cc, - sender = frappe.db.get_single_value('LMS Settings', 'email_sender'), - subject = subject, - send_priority = 0, - queue_separately = True, - message = message, - template=template, - args=args - ) \ No newline at end of file + try: + return frappe.get_doc('Community Member', {'email': frappe.session.user}) + except frappe.DoesNotExistError: + return diff --git a/community/patches.txt b/community/patches.txt index 8ec78bbd..5b1b2f97 100644 --- a/community/patches.txt +++ b/community/patches.txt @@ -1,3 +1,4 @@ community.patches.set_email_preferences community.patches.change_name_for_community_members -community.patches.save_abbr_for_community_members \ No newline at end of file +community.patches.save_abbr_for_community_members +community.patches.create_mentor_request_email_templates diff --git a/community/patches/create_mentor_request_email_templates.py b/community/patches/create_mentor_request_email_templates.py new file mode 100644 index 00000000..93c13f27 --- /dev/null +++ b/community/patches/create_mentor_request_email_templates.py @@ -0,0 +1,31 @@ +from __future__ import unicode_literals +import frappe, os +from frappe import _ + +def execute(): + frappe.reload_doc("email", "doctype", "email_template") + base_path = frappe.get_app_path("community", "templates", "emails") + + if not frappe.db.exists("Email Template", _('Mentor Request Creation Template')): + response = frappe.read_file(os.path.join(base_path, "mentor_request_creation_email.html")) + frappe.get_doc({ + 'doctype': 'Email Template', + 'name': _("Mentor Request Creation Template"), + 'response': response, + 'subject': _('Request for Mentorship'), + 'owner': frappe.session.user + }).insert(ignore_permissions=True) + + frappe.db.set_value("LMS Settings", None, "mentor_request_creation", _('Mentor Request Creation Template')) + + if not frappe.db.exists("Email Template", _('Mentor Request Status Update Template')): + response = frappe.read_file(os.path.join(base_path, "mentor_request_status_update_email.html")) + frappe.get_doc({ + 'doctype': 'Email Template', + 'name': _("Mentor Request Status Update Template"), + 'response': response, + 'subject': _('The status of your application has changed.'), + 'owner': frappe.session.user + }).insert(ignore_permissions=True) + + frappe.db.set_value("LMS Settings", None, "mentor_request_status_update", _('Mentor Request Status Update Template')) diff --git a/community/templates/emails/mentor_request_creation_email.html b/community/templates/emails/mentor_request_creation_email.html new file mode 100644 index 00000000..62e13f49 --- /dev/null +++ b/community/templates/emails/mentor_request_creation_email.html @@ -0,0 +1,9 @@ +
+

Dear {{ member_name }},

+
+

You've applied to become a mentor for this course. Your request is currently under review.

+

If you are not any more interested to mentor this course, you can cancel your application.

+
+

Thanks and Regards,

+

Team Community.

+
diff --git a/community/templates/emails/mentor_request_status_update_email.html b/community/templates/emails/mentor_request_status_update_email.html new file mode 100644 index 00000000..bf2878f0 --- /dev/null +++ b/community/templates/emails/mentor_request_status_update_email.html @@ -0,0 +1,8 @@ +
+

Dear {{ member_name }},

+
+

Your request to join us as a mentor has been {{ status }}.

+
+

Thanks and Regards,

+

Team Community.

+
diff --git a/community/www/courses/course.js b/community/www/courses/course.js index ce08b2e0..3ac859b8 100644 --- a/community/www/courses/course.js +++ b/community/www/courses/course.js @@ -6,7 +6,7 @@ frappe.ready(() => { course: decodeURIComponent($("#course-title").attr("data-course")), }, 'callback': (data) => { - if (data.message) { + if (data.message > 0) { $("#mentor-request").addClass("hide"); $("#already-applied").removeClass("hide") } From 69b3f366f48c228d3329319c850513c772216c59 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Wed, 5 May 2021 16:32:21 +0530 Subject: [PATCH 02/15] feat: widget file added --- community/community/widgets/Avatar.html | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 community/community/widgets/Avatar.html diff --git a/community/community/widgets/Avatar.html b/community/community/widgets/Avatar.html new file mode 100644 index 00000000..e69de29b From dc5b637ada0f0eaaf70040663851ca59ecd15b18 Mon Sep 17 00:00:00 2001 From: Anand Chitipothu Date: Thu, 6 May 2021 06:47:09 +0530 Subject: [PATCH 03/15] refactor: fixed the course page --- .../lms/doctype/lms_course/lms_course.json | 4 +- .../lms/doctype/lms_course/lms_course.py | 23 +--- community/lms/widgets/ChapterTeaser.html | 2 +- community/public/build.json | 2 +- community/public/css/style.css | 84 +------------ community/public/css/style.less | 110 ++++++++++++++++++ community/public/css/vars.css | 3 + community/query.py | 22 ++++ community/www/courses/course.html | 30 ++--- community/www/courses/utils.py | 8 +- community/www/macros/common_macro.html | 44 +++---- 11 files changed, 191 insertions(+), 141 deletions(-) create mode 100644 community/query.py diff --git a/community/lms/doctype/lms_course/lms_course.json b/community/lms/doctype/lms_course/lms_course.json index 0c104240..e9c4d4d7 100644 --- a/community/lms/doctype/lms_course/lms_course.json +++ b/community/lms/doctype/lms_course/lms_course.json @@ -2,7 +2,7 @@ "actions": [], "allow_guest_to_view": 1, "allow_rename": 1, - "autoname": "field:title", + "autoname": "field:slug", "creation": "2021-03-01 16:49:33.622422", "doctype": "DocType", "editable_grid": 1, @@ -89,7 +89,7 @@ "link_fieldname": "course" } ], - "modified": "2021-05-03 05:52:30.396824", + "modified": "2021-05-06 06:06:08.760597", "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 51e8b381..2a731337 100644 --- a/community/lms/doctype/lms_course/lms_course.py +++ b/community/lms/doctype/lms_course/lms_course.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document from ...utils import slugify +from community.query import find, find_all class LMSCourse(Document): @staticmethod @@ -139,24 +140,8 @@ class LMSCourse(Document): now = frappe.utils.nowdate() batches = find_all("LMS Batch", course=self.name, - start_date=[">", now]) + start_date=[">", now], + status="Active", + visibility="Public") return batches -def find_all(doctype, order_by=None, **filters): - """Queries the database for documents of a doctype matching given filters. - """ - rows = frappe.db.get_all(doctype, - filters=filters, - fields='*', - order_by=order_by) - return [frappe.get_doc(dict(row, doctype=doctype)) for row in rows] - -def find(doctype, **filters): - """Queries the database for a document of given doctype matching given filters. - """ - rows = frappe.db.get_all(doctype, - filters=filters, - fields='*') - if rows: - row = rows[0] - return frappe.get_doc(dict(row, doctype=doctype)) diff --git a/community/lms/widgets/ChapterTeaser.html b/community/lms/widgets/ChapterTeaser.html index 51939ac8..c80d1a55 100644 --- a/community/lms/widgets/ChapterTeaser.html +++ b/community/lms/widgets/ChapterTeaser.html @@ -1,6 +1,6 @@
-

{{ chapter.title }}

+

{{index}} {{ chapter.title }}

{{ chapter.description or "" }}
diff --git a/community/public/build.json b/community/public/build.json index cdb08652..6be9391d 100644 --- a/community/public/build.json +++ b/community/public/build.json @@ -3,8 +3,8 @@ "public/css/lms.css" ], "css/community.css": [ - "public/css/vars.css", "public/css/style.css", + "public/css/vars.css", "public/css/style.less" ] } diff --git a/community/public/css/style.css b/community/public/css/style.css index ba05f0a9..d195eab5 100644 --- a/community/public/css/style.css +++ b/community/public/css/style.css @@ -34,82 +34,8 @@ body { margin: 0px; } -.course-header { - margin-top: 20px; - padding: 20px; - background: var(--header-bg); - color: var(--header-color); - border-radius: 9px; -} -.course-author-avatar { - width: 20px; - height: 20px; - border-radius: 50%; - margin-right: 20px; -} - -.course-header h1 { - color: inherit; -} - -.course-type { - text-transform: uppercase; - font-size: 1.0em; - color: var(--tag-color); -} - -.sidebar { - background: var(--sidebar-bg); - margin: 20px 0px; - border-radius: 10px; - padding: 1px 20px 20px 20px; - color: var(--text-color); -} - -.sidebar h3 { - margin-top: 20px; - color: var(--c2); -} - -.sidebar-batch { - background: var(--sidebar-bg); - color: var(--text-color); - position: fixed; - left: 0; - height: 100%; -} - -.sidebar-batch a { - padding: 16px 8px 8px 16px; - display: block; -} - -.instructor { - padding: 10px; -} - -.instructor-title { - font-weight: bold; -} - -.instructor-subtitle { - font-size: 0.8em; - color: var(--text-color); -} - -.sidebar .notice { - padding: 10px; - border-radius: 10px; - border: 1px dashed var(--text-color); -} - -.sidebar .notice a { - color: inherit; - text-decoration: underline; -} - -.course-details { +/* .course-details { margin: 20px 0px; } @@ -118,7 +44,7 @@ body { font-size: 1.4em; font-weight: bold; margin: 20px 0px 10px 0px; -} +} */ .chapter-plan { border-radius: 10px; @@ -133,7 +59,7 @@ body { font-weight: bold; } -.chapter-number { +/* .chapter-number { background: var(--text-color); color: white; border-radius: 50%; @@ -146,7 +72,7 @@ body { .chapter-description { margin: 20px 0px; -} +} */ .lessons { padding-left: 20px; @@ -302,4 +228,4 @@ img.profile-photo { color: #2D005A; font-weight: 600; line-height: 51px; -} \ No newline at end of file +} diff --git a/community/public/css/style.less b/community/public/css/style.less index ffafb8c3..ed0753f5 100644 --- a/community/public/css/style.less +++ b/community/public/css/style.less @@ -5,6 +5,11 @@ body { font-family: "Inter", sans-serif; } +h2 { + margin: 20px 0px; + color: black; +} + .teaser { background: white; border-radius: 9px; @@ -122,3 +127,108 @@ section.lightgray { padding: 2rem 0px; margin-top: 2rem; } + +.course-type { + text-transform: uppercase; + font-size: 1.0em; + font-weight: bold; + color: var(--tag-color); +} + +.course-header { + margin-top: 20px; +} + +/* +.course-header { + margin-top: 20px; + padding: 20px; + background: var(--header-bg); + color: var(--header-color); + border-radius: 9px; +} + +.course-author-avatar { + width: 20px; + height: 20px; + border-radius: 50%; + margin-right: 20px; +} + +.course-header h1 { + color: inherit; +} + +*/ + +// .gray-section { +// background:#F6F6F6; +// border: 1px solid #C4C4C4; +// padding: 20px; +// margin: 20px 0px; +// } + +.instructor-title { + font-weight: bold; + color: black; +} + +.instructor-subtitle { + font-size: 0.8em; + color: var(--text-color); +} + +// .mentors-wrapper { +// .gray-section(); +// } + + +.chapter-number { + background: var(--text-color); + color: white; + border-radius: 50%; + height: 24px; + min-width: 24px; + align-items: center; + padding: 5px 8px 2px 8px; + margin-right: 5px; +} + +.sidebar { + background: var(--sidebar-bg); + border: 1px solid var(--sidebar-border); + margin: 20px 0px; + border-radius: 10px; + padding: 1px 20px 20px 20px; +} + +.sidebar h3 { + margin-top: 20px; + color: black; +} + +.sidebar-batch { + background: var(--sidebar-bg); + color: var(--text-color); + position: fixed; + left: 0; + height: 100%; +} + +.sidebar-batch a { + padding: 16px 8px 8px 16px; + display: block; +} + + +.sidebar .notice { + margin-top: 10px; + padding: 10px; + border-radius: 10px; + border: 1px dashed var(--text-color); +} + +.sidebar .notice a { + color: inherit; + text-decoration: underline; +} diff --git a/community/public/css/vars.css b/community/public/css/vars.css index ff7bba58..b04975fd 100644 --- a/community/public/css/vars.css +++ b/community/public/css/vars.css @@ -1,4 +1,7 @@ /* Define all your css variables here. */ :root { --primary-color: #08B74F; + --tag-color: #737373; + --sidebar-bg: #F6F6F6; + --sidebar-border: #C4C4C4; } diff --git a/community/query.py b/community/query.py new file mode 100644 index 00000000..b2eb58d3 --- /dev/null +++ b/community/query.py @@ -0,0 +1,22 @@ +"""Utilities to find docs. +""" +import frappe + +def find_all(doctype, order_by=None, **filters): + """Queries the database for documents of a doctype matching given filters. + """ + rows = frappe.db.get_all(doctype, + filters=filters, + fields='*', + order_by=order_by) + return [frappe.get_doc(dict(row, doctype=doctype)) for row in rows] + +def find(doctype, **filters): + """Queries the database for a document of given doctype matching given filters. + """ + rows = frappe.db.get_all(doctype, + filters=filters, + fields='*') + if rows: + row = rows[0] + return frappe.get_doc(dict(row, doctype=doctype)) diff --git a/community/www/courses/course.html b/community/www/courses/course.html index 3c08fc97..43614eac 100644 --- a/community/www/courses/course.html +++ b/community/www/courses/course.html @@ -11,22 +11,23 @@
course

{{course.title}}

+
{{ course.short_introduction }}
-
+
+ {{ CourseVideo(course) }} + {{ CourseDescription(course) }} {{ BatchSection(course) }} {{ CourseOutline(course) }}
- -
+
- @@ -35,14 +36,7 @@
{% endblock %} - -{% macro CourseDescription(course) %} -

Course Description

- -
- {{ course.short_introduction }} -
- +{% macro CourseVideo(course) %} {% if course.video_link %}