diff --git a/.gitignore b/.gitignore index f6a99e30..222921e1 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ tags community/docs/current community/public/dist +__pycache__/ +*.py[cod] +*$py.class diff --git a/community/fixtures/custom_field.json b/community/fixtures/custom_field.json new file mode 100644 index 00000000..d3bb2124 --- /dev/null +++ b/community/fixtures/custom_field.json @@ -0,0 +1,108 @@ +[ + { + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "doctype": "Custom Field", + "dt": "User", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "linkedin", + "fieldtype": "Data", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "mobile_no", + "label": "LinkedIn ID", + "length": 0, + "mandatory_depends_on": null, + "modified": "2021-06-30 14:46:55.834145", + "name": "User-linkedin", + "no_copy": 0, + "non_negative": 0, + "options": null, + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "translatable": 1, + "unique": 0, + "width": null + }, + { + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "doctype": "Custom Field", + "dt": "User", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "github", + "fieldtype": "Data", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "linkedin", + "label": "Github ID", + "length": 0, + "mandatory_depends_on": null, + "modified": "2021-06-30 14:46:55.834145", + "name": "User-github", + "no_copy": 0, + "non_negative": 0, + "options": null, + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "translatable": 1, + "unique": 0, + "width": null + } +] diff --git a/community/hooks.py b/community/hooks.py index 5666dd53..61b681f5 100644 --- a/community/hooks.py +++ b/community/hooks.py @@ -104,6 +104,8 @@ doc_events = { # ] #} +fixtures = ["Custom Field"] + # Testing # ------- diff --git a/community/lms/doctype/chapter/chapter.py b/community/lms/doctype/chapter/chapter.py index b616de2c..a563c7e0 100644 --- a/community/lms/doctype/chapter/chapter.py +++ b/community/lms/doctype/chapter/chapter.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document +from ...utils import slugify class Chapter(Document): def get_lessons(self): @@ -13,3 +14,6 @@ class Chapter(Document): fields='name', order_by="index_") return [frappe.get_doc('Lesson', row['name']) for row in rows] + + def get_slugified_chapter_title(self): + return slugify(self.title) diff --git a/community/lms/doctype/lesson/lesson.py b/community/lms/doctype/lesson/lesson.py index d90fa2c1..3b66ac0c 100644 --- a/community/lms/doctype/lesson/lesson.py +++ b/community/lms/doctype/lesson/lesson.py @@ -63,32 +63,35 @@ class Lesson(Document): return @frappe.whitelist() -def save_progress(lesson, course): +def save_progress(lesson, course, status): if not frappe.db.exists("LMS Batch Membership", { "member": frappe.session.user, "course": course }): return + if frappe.db.exists("LMS Course Progress", { "lesson": lesson, - "owner": frappe.session.user + "owner": frappe.session.user, + "course": course }): - return - - lesson_details = frappe.get_doc("Lesson", lesson) - dynamic_content = find_macros(lesson_details.body) - - status = "Complete" - if dynamic_content: - status = "Partially Complete" - - frappe.get_doc({ - "doctype": "LMS Course Progress", - "lesson": lesson_details.name, - "status": status - }).save(ignore_permissions=True) + doc = frappe.get_doc("LMS Course Progress", + { + "lesson": lesson, + "owner": frappe.session.user, + "course": course + }) + doc.status = status + doc.save(ignore_permissions=True) + else: + frappe.get_doc({ + "doctype": "LMS Course Progress", + "lesson": lesson, + "status": status, + }).save(ignore_permissions=True) + return "OK" def update_progress(lesson): user = frappe.session.user diff --git a/community/lms/doctype/lms_batch_membership/lms_batch_membership.js b/community/lms/doctype/lms_batch_membership/lms_batch_membership.js index 0a20239d..68ac4f55 100644 --- a/community/lms/doctype/lms_batch_membership/lms_batch_membership.js +++ b/community/lms/doctype/lms_batch_membership/lms_batch_membership.js @@ -10,5 +10,5 @@ frappe.ui.form.on('LMS Batch Membership', { } }; }); - }, + } }); diff --git a/community/lms/doctype/lms_batch_membership/lms_batch_membership.json b/community/lms/doctype/lms_batch_membership/lms_batch_membership.json index ba43cd69..f7f1f2dc 100644 --- a/community/lms/doctype/lms_batch_membership/lms_batch_membership.json +++ b/community/lms/doctype/lms_batch_membership/lms_batch_membership.json @@ -65,8 +65,7 @@ "fieldname": "course", "fieldtype": "Data", "in_list_view": 1, - "label": "Course", - "read_only": 1 + "label": "Course" }, { "fieldname": "current_lesson", @@ -84,7 +83,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-06-21 12:10:28.808803", + "modified": "2021-07-06 20:50:46.885325", "modified_by": "Administrator", "module": "LMS", "name": "LMS Batch Membership", diff --git a/community/lms/doctype/lms_course/lms_course.json b/community/lms/doctype/lms_course/lms_course.json index 8b46e3ec..a654c9e0 100644 --- a/community/lms/doctype/lms_course/lms_course.json +++ b/community/lms/doctype/lms_course/lms_course.json @@ -21,12 +21,14 @@ "engine": "InnoDB", "field_order": [ "title", - "is_published", - "disable_self_learning", - "column_break_3", "short_code", "video_link", + "column_break_3", + "is_published", + "disable_self_learning", + "image", "section_break_5", + "tags", "short_introduction", "description" ], @@ -80,6 +82,16 @@ "fieldname": "disable_self_learning", "fieldtype": "Check", "label": "Disable Self Learning" + }, + { + "fieldname": "image", + "fieldtype": "Attach Image", + "label": "Preview Image" + }, + { + "fieldname": "tags", + "fieldtype": "Data", + "label": "Tags" } ], "index_web_pages_for_search": 1, @@ -106,7 +118,7 @@ "link_fieldname": "course" } ], - "modified": "2021-06-21 11:34:04.552376", + "modified": "2021-07-09 15:05:05.372430", "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 a98a5e63..73b1d5e3 100644 --- a/community/lms/doctype/lms_course/lms_course.py +++ b/community/lms/doctype/lms_course/lms_course.py @@ -8,7 +8,7 @@ from frappe.model.document import Document import json from ...utils import slugify from community.query import find, find_all -from frappe.utils import flt +from frappe.utils import flt, cint class LMSCourse(Document): @staticmethod @@ -115,6 +115,28 @@ class LMSCourse(Document): # TODO: chapters should have a way to specify the order return find_all("Chapter", course=self.name, order_by="index_") + def get_lessons(self): + """ Returns all lessons of this course """ + lessons = [] + chapters = self.get_chapters() + for chapter in chapters: + lessons.append(frappe.get_all("Lesson", {"chapter": chapter.name})) + return lessons + + def get_course_progress(self): + """ Returns the course progress of the session user """ + lesson_count = len(self.get_lessons()) + completed_lessons = frappe.db.count("LMS Course Progress", + { + "course": self.name, + "owner": frappe.session.user, + "status": "Complete" + }) + precision = cint(frappe.db.get_default("float_precision")) or 3 + if not lesson_count: + return 0 + return flt(((completed_lessons/lesson_count) * 100), precision) + def get_batch(self, batch_name): return find("LMS Batch", name=batch_name, course=self.name) @@ -199,7 +221,12 @@ class LMSCourse(Document): } if batch: filters["batch"] = batch - membership = frappe.db.get_value("LMS Batch Membership", filters, ["name","batch", "current_lesson"], as_dict=True) + + membership = frappe.db.get_value("LMS Batch Membership", + filters, + ["name", "batch", "current_lesson", "member_type"], + as_dict=True) + if membership and membership.batch: membership.batch_title = frappe.db.get_value("LMS Batch", membership.batch, "title") return membership @@ -241,9 +268,52 @@ 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", + { + "course": self.name + }, + ["review", "rating", "owner"], + order_by= "creation desc") + + for review in reviews: + review.owner_details = frappe.get_doc("User", review.owner) + + return reviews + + def is_eligible_to_review(self, membership): + """ Checks if user is eligible to review the course """ + if not membership: + return False + if frappe.db.count("LMS Course Review", + { + "course": self.name, + "owner": frappe.session.user + }): + return False + return True + + def get_average_rating(self): + ratings = [review.rating for review in self.get_reviews()] + if not len(ratings): + return None + return sum(ratings)/len(ratings) + def get_outline(self): return CourseOutline(self) + def get_progress(self, lesson): + return frappe.db.get_value("LMS Course Progress", + { + "course": self.name, + "owner": frappe.session.user, + "lesson": lesson + }, + ["status"]) + class CourseOutline: def __init__(self, course): self.course = course diff --git a/community/lms/doctype/lms_course_mentor_mapping/lms_course_mentor_mapping.js b/community/lms/doctype/lms_course_mentor_mapping/lms_course_mentor_mapping.js index 5b17f61a..c995587f 100644 --- a/community/lms/doctype/lms_course_mentor_mapping/lms_course_mentor_mapping.js +++ b/community/lms/doctype/lms_course_mentor_mapping/lms_course_mentor_mapping.js @@ -2,7 +2,13 @@ // For license information, please see license.txt frappe.ui.form.on('LMS Course Mentor Mapping', { - // refresh: function(frm) { - - // } + onload: function(frm) { + frm.set_query('mentor', function(doc) { + return { + filters: { + "ignore_user_type": 1, + } + }; + }); + }, }); diff --git a/community/lms/doctype/lms_course_review/__init__.py b/community/lms/doctype/lms_course_review/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/community/lms/doctype/lms_course_review/lms_course_review.js b/community/lms/doctype/lms_course_review/lms_course_review.js new file mode 100644 index 00000000..8382eb52 --- /dev/null +++ b/community/lms/doctype/lms_course_review/lms_course_review.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, FOSS United and contributors +// For license information, please see license.txt + +frappe.ui.form.on('LMS Course Review', { + // refresh: function(frm) { + + // } +}); diff --git a/community/lms/doctype/lms_course_review/lms_course_review.json b/community/lms/doctype/lms_course_review/lms_course_review.json new file mode 100644 index 00000000..f2b78b5d --- /dev/null +++ b/community/lms/doctype/lms_course_review/lms_course_review.json @@ -0,0 +1,57 @@ +{ + "actions": [], + "creation": "2021-06-28 13:36:36.146718", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "review", + "rating", + "course" + ], + "fields": [ + { + "fieldname": "review", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Review" + }, + { + "fieldname": "rating", + "fieldtype": "Rating", + "in_list_view": 1, + "label": "Rating" + }, + { + "fieldname": "course", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Course", + "options": "LMS Course" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-07-05 14:57:03.841430", + "modified_by": "Administrator", + "module": "LMS", + "name": "LMS Course Review", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/community/lms/doctype/lms_course_review/lms_course_review.py b/community/lms/doctype/lms_course_review/lms_course_review.py new file mode 100644 index 00000000..2c4199a4 --- /dev/null +++ b/community/lms/doctype/lms_course_review/lms_course_review.py @@ -0,0 +1,18 @@ +# Copyright (c) 2021, FOSS United and contributors +# For license information, please see license.txt + +import frappe +from frappe.model.document import Document + +class LMSCourseReview(Document): + pass + +@frappe.whitelist() +def submit_review(rating, review, course): + frappe.get_doc({ + "doctype": "LMS Course Review", + "rating": rating, + "review": review, + "course": course + }).save(ignore_permissions=True) + return "OK" diff --git a/community/lms/doctype/lms_course_review/test_lms_course_review.py b/community/lms/doctype/lms_course_review/test_lms_course_review.py new file mode 100644 index 00000000..da8a1450 --- /dev/null +++ b/community/lms/doctype/lms_course_review/test_lms_course_review.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, FOSS United and Contributors +# See license.txt + +# import frappe +import unittest + +class TestLMSCourseReview(unittest.TestCase): + pass diff --git a/community/lms/md.py b/community/lms/md.py index 0658ca82..73be75d9 100644 --- a/community/lms/md.py +++ b/community/lms/md.py @@ -93,11 +93,11 @@ class MacroInlineProcessor(InlineProcessor): macro = m.group(1) arg = m.group(2) html = render_macro(macro, arg) - html = sanitize_html(str(html)) + html = sanitize_html(str(html), macro) e = etree.fromstring(html) return e, m.start(0), m.end(0) -def sanitize_html(html): +def sanitize_html(html, macro): """Sanotize the html using BeautifulSoup. The markdown processor request the correct markup and crashes on @@ -106,4 +106,7 @@ def sanitize_html(html): """ soup = BeautifulSoup(html, features="lxml") nodes = soup.body.children - return "
" + "\n".join(str(node) for node in nodes) + "
" + classname = "" + if macro == "YouTubeVideo": + classname = "lesson-video" + return "
" + "\n".join(str(node) for node in nodes) + "
" diff --git a/community/lms/web_form/profile/__init__.py b/community/lms/web_form/profile/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/community/lms/web_form/profile/profile.js b/community/lms/web_form/profile/profile.js new file mode 100644 index 00000000..699703c5 --- /dev/null +++ b/community/lms/web_form/profile/profile.js @@ -0,0 +1,3 @@ +frappe.ready(function() { + // bind events here +}) \ No newline at end of file diff --git a/community/lms/web_form/profile/profile.json b/community/lms/web_form/profile/profile.json new file mode 100644 index 00000000..c4962a94 --- /dev/null +++ b/community/lms/web_form/profile/profile.json @@ -0,0 +1,125 @@ +{ + "accept_payment": 0, + "allow_comments": 0, + "allow_delete": 0, + "allow_edit": 1, + "allow_incomplete": 0, + "allow_multiple": 0, + "allow_print": 0, + "amount": 0.0, + "amount_based_on_field": 0, + "apply_document_permissions": 0, + "breadcrumbs": "", + "button_label": "Save", + "creation": "2021-06-30 13:48:13.682851", + "doc_type": "User", + "docstatus": 0, + "doctype": "Web Form", + "idx": 0, + "is_standard": 1, + "login_required": 1, + "max_attachment_size": 0, + "modified": "2021-06-30 15:53:20.967466", + "modified_by": "Administrator", + "module": "LMS", + "name": "profile", + "owner": "Administrator", + "payment_button_label": "Buy Now", + "published": 1, + "route": "profile", + "route_to_success_link": 0, + "show_attachments": 0, + "show_in_grid": 0, + "show_sidebar": 0, + "sidebar_items": [], + "success_url": "/profile", + "title": "Profile", + "web_form_fields": [ + { + "allow_read_on_all_link_options": 0, + "fieldname": "first_name", + "fieldtype": "Data", + "hidden": 0, + "label": "First Name", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 1, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "middle_name", + "fieldtype": "Data", + "hidden": 0, + "label": "Middle Name (Optional)", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "last_name", + "fieldtype": "Data", + "hidden": 0, + "label": "Last Name", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "description": "Get your globally recognized avatar from Gravatar.com", + "fieldname": "user_image", + "fieldtype": "Attach Image", + "hidden": 0, + "label": "User Image", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "username", + "fieldtype": "Data", + "hidden": 0, + "label": "Username", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "mobile_no", + "fieldtype": "Data", + "hidden": 0, + "label": "Mobile No", + "max_length": 0, + "max_value": 0, + "options": "Phone", + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "bio", + "fieldtype": "Small Text", + "hidden": 0, + "label": "Bio", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + } + ] +} \ No newline at end of file diff --git a/community/lms/web_form/profile/profile.py b/community/lms/web_form/profile/profile.py new file mode 100644 index 00000000..e1ada619 --- /dev/null +++ b/community/lms/web_form/profile/profile.py @@ -0,0 +1,5 @@ +import frappe + +def get_context(context): + # do your magic here + pass diff --git a/community/lms/widgets/BreadCrumb.html b/community/lms/widgets/BreadCrumb.html new file mode 100644 index 00000000..750ca9ef --- /dev/null +++ b/community/lms/widgets/BreadCrumb.html @@ -0,0 +1,19 @@ + diff --git a/community/lms/widgets/ChapterTeaser.html b/community/lms/widgets/ChapterTeaser.html index 5a49f5b3..526e92fb 100644 --- a/community/lms/widgets/ChapterTeaser.html +++ b/community/lms/widgets/ChapterTeaser.html @@ -1,28 +1,98 @@ -
-
-
{{index}}. {{ chapter.title }}
-
- {{ chapter.description or "" }} +
+ + + + {% endblock %} - -{% macro course_card(course) %} - -{% endmacro %} +{% block script %} + +{% endblock %} diff --git a/community/www/courses/index.py b/community/www/courses/index.py index 49b1864a..86d6acf2 100644 --- a/community/www/courses/index.py +++ b/community/www/courses/index.py @@ -5,8 +5,8 @@ def get_context(context): context.courses = get_courses() def get_courses(): - courses = frappe.get_all( - "LMS Course", - fields=['name', 'title', 'description'] - ) + course_names = frappe.get_all("LMS Course", pluck="name") + courses = [] + for course in course_names: + courses.append(frappe.get_doc("LMS Course", course)) return courses diff --git a/community/www/dashboard/__init__.py b/community/www/dashboard/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/community/www/dashboard/__pycache__/__init__.py b/community/www/dashboard/__pycache__/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/community/www/hackathons/__init__.py b/community/www/hackathons/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/community/www/hackathons/__pycache__/__init__.py b/community/www/hackathons/__pycache__/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/community/www/hackathons/macros/__init__.py b/community/www/hackathons/macros/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/community/www/home/__init__.py b/community/www/home/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/community/www/home/__pycache__/__init__.py b/community/www/home/__pycache__/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/community/www/macros/__init__.py b/community/www/macros/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/community/www/my-courses/__init__.py b/community/www/my-courses/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/community/www/my-courses/__pycache__/__init__.py b/community/www/my-courses/__pycache__/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/community/www/profiles/__init__.py b/community/www/profiles/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/community/www/profiles/__pycache__/__init__.py b/community/www/profiles/__pycache__/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/community/www/profiles/profile.html b/community/www/profiles/profile.html index 68e9a58c..325565c6 100644 --- a/community/www/profiles/profile.html +++ b/community/www/profiles/profile.html @@ -1,100 +1,116 @@ -{% extends "templates/web.html" %} +{% extends "templates/base.html" %} {% block head_include %} - {% endblock %} -{% block page_content %} -
-
- {{ widgets.Avatar(member=member, avatar_class="avatar-xl") }} -
-
- -
- -
-
- {% for tab in profile_tabs %} - {% set slug = title.lower().replace(" ", "-") %} -
-
- {{ tab.render() }} -
-
- {% endfor %} + +{% block content %} +
+
+ {{ widgets.MemberCard(member=member, show_course_count=True, dimension_class="member-card-xl") }} + {{ AboutOverviewSection(member) }} +
+ {{ CoursesCreated(member) }} + {{ CoursesMentored(member) }} + {{ CoursesEnrolled(member) }}
+ {{ ProfileTabs(profile_tabs) }}
{% endblock %} - +{% macro AboutOverviewSection(member) %} +
+ {% if member.bio %} +
+
+ About +
+
+ {{ member.bio }} +
+
+ {% endif %} +
+
+ Overview +
+
+ {% if member.get_course_membership("Student") | length %} +
+ + {{ member.get_course_membership("Student") | length }} Enrolled +
+ {% endif %} + {% if member.get_user_reviews() | length %} +
+ + {{ member.get_user_reviews() | length }} Created +
+ {% endif %} + {% if member.get_course_membership("Mentor") | length%} +
+ + {{ member.get_course_membership("Mentor") | length }} Mentored +
+ {% endif %} +
+
+
+{% endmacro %} + + +{% macro CoursesCreated(member) %} +{% if member.get_authored_courses() | length %} +
+ Courses Created +
+
+ {% for course in member.get_authored_courses() %} + {% set course_details = frappe.get_doc("LMS Course", course) %} + {{ widgets.CourseCard(course=course_details) }} + {% endfor %} +
+{% endif %} +{% endmacro %} + +{% macro CoursesMentored(member) %} +{% if member.get_course_membership("Mentor") | length %} +
+ Courses Mentored +
+
+ {% for membership in member.get_course_membership("Mentor") %} + {% set course_details = frappe.get_doc("LMS Course", membership.course) %} + {{ widgets.CourseCard(course=course_details) }} + {% endfor %} +
+{% endif %} +{% endmacro %} + +{% macro CoursesEnrolled(member) %} +{% if member.get_course_membership("Student") | length %} +
+ Courses Enrolled +
+
+ {% for membership in member.get_course_membership("Student") %} + {% set course_details = frappe.get_doc("LMS Course", membership.course) %} + {{ widgets.CourseCard(course=course_details) }} + {% endfor %} +
+{% endif %} +{% endmacro %} + +{% macro ProfileTabs(profile_tabs) %} +
+ {% for tab in profile_tabs %} + {% set slug = title.lower().replace(" ", "-") %} +
+
+ {{ tab.render() }} +
+
+ {% endfor %} +
+{% endmacro %} diff --git a/community/www/sketches/__init__.py b/community/www/sketches/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/community/www/sketches/__pycache__/__init__.py b/community/www/sketches/__pycache__/__init__.py new file mode 100644 index 00000000..e69de29b