diff --git a/community/community/doctype/community_member/community_member.py b/community/community/doctype/community_member/community_member.py index 333771f0..dd7bed8b 100644 --- a/community/community/doctype/community_member/community_member.py +++ b/community/community/doctype/community_member/community_member.py @@ -8,6 +8,8 @@ import re from frappe import _ from frappe.model.document import Document import random +from frappe.utils import cint +import hashlib class CommunityMember(Document): @@ -45,6 +47,24 @@ class CommunityMember(Document): 'member_type': 'Mentor' }) + def get_palette(self): + palette = [ + ['--orange-avatar-bg', '--orange-avatar-color'], + ['--pink-avatar-bg', '--pink-avatar-color'], + ['--blue-avatar-bg', '--blue-avatar-color'], + ['--green-avatar-bg', '--green-avatar-color'], + ['--dark-green-avatar-bg', '--dark-green-avatar-color'], + ['--red-avatar-bg', '--red-avatar-color'], + ['--yellow-avatar-bg', '--yellow-avatar-color'], + ['--purple-avatar-bg', '--purple-avatar-color'], + ['--gray-avatar-bg', '--gray-avatar-color0'] + ] + + encoded_name = str(self.full_name).encode("utf-8") + hash_name = hashlib.md5(encoded_name).hexdigest() + idx = cint((int(hash_name[4:6], 16) + 1) / 5.33) + return palette[idx % 8] + def __repr__(self): return f"" diff --git a/community/community/widgets/Avatar.html b/community/community/widgets/Avatar.html new file mode 100644 index 00000000..e0c88cf5 --- /dev/null +++ b/community/community/widgets/Avatar.html @@ -0,0 +1,14 @@ +{% set color = member.get_palette() %} + + + {% if member.photo %} + + + {% else %} + + {{ member.abbr }} + + {% endif %} + + diff --git a/community/lms/doctype/lms_batch/lms_batch.json b/community/lms/doctype/lms_batch/lms_batch.json index 8f0b94f0..6eb5056e 100644 --- a/community/lms/doctype/lms_batch/lms_batch.json +++ b/community/lms/doctype/lms_batch/lms_batch.json @@ -1,6 +1,5 @@ { "actions": [], - "autoname": "field:title", "creation": "2021-03-18 19:37:34.614796", "doctype": "DocType", "editable_grid": 1, @@ -51,10 +50,12 @@ "label": "Description" }, { + "default": "Public", "fieldname": "visibility", "fieldtype": "Select", + "in_list_view": 1, "label": "Visibility", - "options": "\nPublic\nUnlisted\nPrivate" + "options": "Public\nUnlisted\nPrivate" }, { "fieldname": "membership", @@ -63,16 +64,19 @@ "options": "\nOpen\nRestricted\nInvite Only\nClosed" }, { + "default": "Active", "fieldname": "status", "fieldtype": "Select", + "in_list_view": 1, "label": "Status", - "options": "\nActive\nInactive" + "options": "Active\nInactive" }, { + "default": "Ready", "fieldname": "stage", "fieldtype": "Select", "label": "Stage", - "options": "\nReady\nIn Progress\nCompleted\nCancelled" + "options": "Ready\nIn Progress\nCompleted\nCancelled" }, { "fieldname": "column_break_3", @@ -122,7 +126,7 @@ "link_fieldname": "batch" } ], - "modified": "2021-04-30 09:52:18.941276", + "modified": "2021-05-06 05:46:38.469120", "modified_by": "Administrator", "module": "LMS", "name": "LMS Batch", diff --git a/community/lms/doctype/lms_batch/lms_batch.py b/community/lms/doctype/lms_batch/lms_batch.py index 752e7737..0960cd13 100644 --- a/community/lms/doctype/lms_batch/lms_batch.py +++ b/community/lms/doctype/lms_batch/lms_batch.py @@ -8,6 +8,7 @@ from frappe.model.document import Document from community.www.courses.utils import get_member_with_email from frappe import _ from community.lms.doctype.lms_batch_membership.lms_batch_membership import create_membership +from community.query import find, find_all class LMSBatch(Document): def validate(self): @@ -29,15 +30,20 @@ class LMSBatch(Document): self.code = short_code + str(len(course_batches) + 1) def get_mentors(self): - mentors = [] memberships = frappe.get_all( "LMS Batch Membership", {"batch": self.name, "member_type": "Mentor"}, ["member"]) - for membership in memberships: - member = frappe.db.get_value("Community Member", membership.member, ["full_name", "photo", "abbr"], as_dict=1) - mentors.append(member) - return mentors + member_names = [m['member'] for m in memberships] + return find_all("Community Member", name=["IN", member_names]) + + def is_member(self, email): + """Checks if a person is part of a batch. + """ + member = find("Community Member", email=email) + return member and frappe.db.exists( + "LMS Batch Membership", + {"batch": self.name, "member": member.name}) @frappe.whitelist() def get_messages(batch): diff --git a/community/lms/doctype/lms_course/lms_course.json b/community/lms/doctype/lms_course/lms_course.json index 0c104240..9ec683b6 100644 --- a/community/lms/doctype/lms_course/lms_course.json +++ b/community/lms/doctype/lms_course/lms_course.json @@ -2,14 +2,12 @@ "actions": [], "allow_guest_to_view": 1, "allow_rename": 1, - "autoname": "field:title", "creation": "2021-03-01 16:49:33.622422", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ "title", - "slug", "is_published", "column_break_3", "short_code", @@ -43,14 +41,6 @@ "fieldtype": "Data", "label": "Short Code" }, - { - "description": "The slug of the course. Autogenerated from the title if not specified.", - "fieldname": "slug", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Slug", - "unique": 1 - }, { "fieldname": "column_break_3", "fieldtype": "Column Break" @@ -87,9 +77,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-06 13:37:03.318829", "modified_by": "Administrator", "module": "LMS", "name": "LMS Course", @@ -108,7 +103,7 @@ "write": 1 } ], - "search_fields": "slug", + "search_fields": "title", "sort_field": "creation", "sort_order": "DESC", "title_field": "title", diff --git a/community/lms/doctype/lms_course/lms_course.py b/community/lms/doctype/lms_course/lms_course.py index 51e8b381..7dea7153 100644 --- a/community/lms/doctype/lms_course/lms_course.py +++ b/community/lms/doctype/lms_course/lms_course.py @@ -6,13 +6,18 @@ 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 - def find(slug): - """Returns the course with specified slug. + def find(name): + """Returns the course with specified name. """ - return find("LMS Course", is_published=True, slug=slug) + return find("LMS Course", is_published=True, name=name) + + def autoname(self): + if not self.name: + self.name = self.generate_slug(title=self.title) @staticmethod def find_all(): @@ -20,19 +25,15 @@ class LMSCourse(Document): """ return find_all("LMS Course", is_published=True) - def before_save(self): - if not self.slug: - self.slug = self.generate_slug(title=self.title) - def generate_slug(self, title): result = frappe.get_all( 'LMS Course', - fields=['slug']) - slugs = set([row['slug'] for row in result]) + fields=['name']) + slugs = set([row['name'] for row in result]) return slugify(title, used_slugs=slugs) def __repr__(self): - return f"" + return f"" def get_topic(self, slug): """Returns the topic with given slug in this course as a Document. @@ -123,6 +124,9 @@ class LMSCourse(Document): # TODO: chapters should have a way to specify the order return find_all("Chapter", course=self.name, order_by="creation") + def get_batch(self, batch_name): + return find("LMS Batch", name=batch_name, course=self.name) + def get_batches(self, mentor=None): batches = find_all("LMS Batch", course=self.name) if mentor: @@ -139,24 +143,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/doctype/lms_course/test_lms_course.py b/community/lms/doctype/lms_course/test_lms_course.py index a54ab337..61a3ecf8 100644 --- a/community/lms/doctype/lms_course/test_lms_course.py +++ b/community/lms/doctype/lms_course/test_lms_course.py @@ -24,7 +24,7 @@ class TestLMSCourse(unittest.TestCase): def test_new_course(self): course = self.new_course("Test Course") assert course.title == "Test Course" - assert course.slug == "test-course" + assert course.name == "test-course" assert course.get_mentors() == [] def test_find_all(self): @@ -41,7 +41,7 @@ class TestLMSCourse(unittest.TestCase): # now we should find one course courses = LMSCourse.find_all() - assert [c.slug for c in courses] == [course.slug] + assert [c.name for c in courses] == [course.name] # disabled this test as it is failing def _test_add_mentors(self): 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..d9162bdf 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,119 @@ 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", "title"], as_dict=True) + message = frappe.render_template(email_template.response, + { + 'member_name': member.full_name, + 'course_url': '/courses/' + course_details.slug, + 'course': course_details.title + }) + + 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 + + course_details = frappe.db.get_value("LMS Course", self.course, ["owner", "title"], as_dict=True) + message = frappe.render_template(email_template.response, + { + 'member_name': self.member_name, + 'status': self.status, + 'course': course_details.title + }) + + 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') + 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/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/lms/widgets/CourseTeaser.html b/community/lms/widgets/CourseTeaser.html index 453ce9ff..029db9ae 100644 --- a/community/lms/widgets/CourseTeaser.html +++ b/community/lms/widgets/CourseTeaser.html @@ -1,13 +1,15 @@
-

{{ course.title }}

+

{{ course.title }}

{{ course.short_introduction or "" }}
diff --git a/community/lms/widgets/RequestInvite.html b/community/lms/widgets/RequestInvite.html index c198ec97..ce3d8799 100644 --- a/community/lms/widgets/RequestInvite.html +++ b/community/lms/widgets/RequestInvite.html @@ -1,6 +1,12 @@
- - Request Invite +
+
+ +
+ +