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..8158f3df --- /dev/null +++ b/community/community/widgets/Avatar.html @@ -0,0 +1,12 @@ +{% 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 7fc547f3..9e4d5fe3 100644 --- a/community/lms/doctype/lms_batch/lms_batch.py +++ b/community/lms/doctype/lms_batch/lms_batch.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document from community.www.courses.utils import get_member_with_email +from community.query import find, find_all class LMSBatch(Document): def validate(self): @@ -23,10 +24,16 @@ class LMSBatch(Document): "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_batch_membership/lms_batch_membership.py b/community/lms/doctype/lms_batch_membership/lms_batch_membership.py index a9f963a3..5c55bab6 100644 --- a/community/lms/doctype/lms_batch_membership/lms_batch_membership.py +++ b/community/lms/doctype/lms_batch_membership/lms_batch_membership.py @@ -40,6 +40,6 @@ def create_membership(batch, course=None, member=None, member_type="Student", ro "member": member }).save(ignore_permissions=True) if course: - course_slug = frappe.db.get_value("LMS Course", {"title": course}, ["slug"]) + course_slug = frappe.db.get_value("LMS Course", {"title": course}, ["name"]) return course_slug return "OK" diff --git a/community/lms/doctype/lms_course/lms_course.json b/community/lms/doctype/lms_course/lms_course.json index cad65d29..1ec13d08 100644 --- a/community/lms/doctype/lms_course/lms_course.json +++ b/community/lms/doctype/lms_course/lms_course.json @@ -2,7 +2,6 @@ "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, @@ -94,7 +93,7 @@ "link_fieldname": "course" } ], - "modified": "2021-05-05 14:17:26.297602", + "modified": "2021-05-06 11:15:45.728976", "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..5d8c07e6 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,10 +25,6 @@ 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', @@ -32,7 +33,7 @@ class LMSCourse(Document): 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/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..525d94c1 100644 --- a/community/lms/widgets/CourseTeaser.html +++ b/community/lms/widgets/CourseTeaser.html @@ -1,6 +1,6 @@
-

{{ course.title }}

+

{{ course.title }}

{{ course.short_introduction 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/widgets.py b/community/widgets.py index b5475ecc..1e135187 100644 --- a/community/widgets.py +++ b/community/widgets.py @@ -14,7 +14,8 @@ from frappe.utils.jinja import get_jenv # When {{widgets.SomeWidget()}} is called, it looks for # widgets/SomeWidgets.html in each of these modules. MODULES = [ - "lms" + "lms", + "community" ] def update_website_context(context): diff --git a/community/www/courses/about/index.html b/community/www/courses/about/index.html index ef417228..1ea0ab1f 100644 --- a/community/www/courses/about/index.html +++ b/community/www/courses/about/index.html @@ -9,19 +9,16 @@ {% endblock %} {% block content %} -{{ Sidebar(course_slug, batch_code) }} +{{ Sidebar(course.name, batch.name) }}
{{ CourseBasicDetail(course)}} - {{ InstructorsSection(instructor) }} - {% if batch.description %} - {{ BatchDetails(batch.description) }} - {% endif %} - {{ MentorsSection(mentors, True, course.name) }} + {{ InstructorsSection(course.get_instructor()) }} + {{ BatchDetails(batch)}}
{% endblock %} {% macro CourseBasicDetail(course) %} -

{{course.name}}

+

{{course.title}}

{{course.short_introduction}}
@@ -36,11 +33,25 @@
{{frappe.utils.md_to_html(course.description)}}
{% endmacro %} -{% macro BatchDetails(description) %} -
-

About the Batch

-
- {{ frappe.utils.md_to_html(description) }} -
+{% macro BatchDetails(batch) %} +

About the Batch

+ +
+
+
Session every {{batch.sessions_on}}
+
{{frappe.utils.format_time(batch.start_time, "short")}} - + {{frappe.utils.format_time(batch.end_time, "short")}}
+
Starting {{frappe.utils.format_date(batch.start_date, "medium")}}
+
mentors
+ + {% for m in batch.get_mentors() %} +
+ {% if m.photo_url %} + + {% endif %} + {{m.full_name}} +
+ {% endfor %} +
{% endmacro %} diff --git a/community/www/courses/about/index.py b/community/www/courses/about/index.py index 780fee44..83b3df0e 100644 --- a/community/www/courses/about/index.py +++ b/community/www/courses/about/index.py @@ -1,21 +1,21 @@ import frappe -from community.www.courses.utils import redirect_if_not_a_member, get_course, get_instructor, get_batch +from community.lms.models import Course def get_context(context): context.no_cache = 1 - context.course_slug = frappe.form_dict["course"] - context.course = get_course(context.course_slug) - context.batch_code = frappe.form_dict["batch"] - redirect_if_not_a_member(context.course_slug, context.batch_code) - context.instructor = get_instructor(context.course.owner) - context.batch = get_batch(context.batch_code) - context.mentors = get_mentors(context.batch.name) + course_name = frappe.form_dict["course"] + batch_name = frappe.form_dict["batch"] -def get_mentors(batch): - mentors = [] - memberships = frappe.get_all("LMS Batch Membership", {"batch": batch, "member_type": "Mentor"}, ["member"]) - for membership in memberships: - member = frappe.get_doc("Community Member", membership.member) - mentors.append(member) - return mentors + course = Course.find(course_name) + if not course: + context.template = "www/404.html" + return + + batch = course.get_batch(batch_name) + if not batch: + frappe.local.flags.redirect_location = "/courses/" + course_name + raise frappe.Redirect + + context.course = course + context.batch = batch diff --git a/community/www/courses/course.html b/community/www/courses/course.html index 3c08fc97..4b1cb29a 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 %}