Merge branch 'main' into mentor-request-email-templates
This commit is contained in:
@@ -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"<CommunityMember: {self.email}>"
|
||||
|
||||
|
||||
12
community/community/widgets/Avatar.html
Normal file
12
community/community/widgets/Avatar.html
Normal file
@@ -0,0 +1,12 @@
|
||||
{% set color = member.get_palette() %}
|
||||
<span class="avatar {{ avatar_class }}" title="{{ member.full_name }}">
|
||||
{% if member.photo %}
|
||||
<img class="avatar-frame standard-image" src="{{ member.photo }}" title="{{ member.full_name }}">
|
||||
</img>
|
||||
{% else %}
|
||||
<span class="avatar-frame standard-image" title="{{ member.full_name }}"
|
||||
style="background-color: var({{color[0]}}); color: var({{color[1]}});">
|
||||
{{ member.abbr }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
@@ -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",
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"<Course#{self.name} {self.slug}>"
|
||||
return f"<Course#{self.name}>"
|
||||
|
||||
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))
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="chapter-teaser">
|
||||
<div class="teaser-body">
|
||||
<h3 class="chapter-title">{{ chapter.title }}</h3>
|
||||
<h3 class="chapter-title"><span class="chapter-number">{{index}}</span> {{ chapter.title }}</h3>
|
||||
<div class="chapter-description">
|
||||
{{ chapter.description or "" }}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="course-teaser">
|
||||
<div class="course-body">
|
||||
<h3 class="course-title"><a href="/courses/{{ course.slug }}">{{ course.title }}</a></h3>
|
||||
<h3 class="course-title"><a href="/courses/{{ course.name }}">{{ course.title }}</a></h3>
|
||||
<div class="course-intro">
|
||||
{{ course.short_introduction or "" }}
|
||||
</div>
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
/* Define all your css variables here. */
|
||||
:root {
|
||||
--primary-color: #08B74F;
|
||||
--tag-color: #737373;
|
||||
--sidebar-bg: #F6F6F6;
|
||||
--sidebar-border: #C4C4C4;
|
||||
}
|
||||
|
||||
22
community/query.py
Normal file
22
community/query.py
Normal file
@@ -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))
|
||||
@@ -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):
|
||||
|
||||
@@ -9,19 +9,16 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{{ Sidebar(course_slug, batch_code) }}
|
||||
{{ Sidebar(course.name, batch.name) }}
|
||||
<div class="container">
|
||||
{{ CourseBasicDetail(course)}}
|
||||
{{ InstructorsSection(instructor) }}
|
||||
{% if batch.description %}
|
||||
{{ BatchDetails(batch.description) }}
|
||||
{% endif %}
|
||||
{{ MentorsSection(mentors, True, course.name) }}
|
||||
{{ InstructorsSection(course.get_instructor()) }}
|
||||
{{ BatchDetails(batch)}}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% macro CourseBasicDetail(course) %}
|
||||
<h2>{{course.name}}</h2>
|
||||
<h2>{{course.title}}</h2>
|
||||
<div class="course-description">
|
||||
{{course.short_introduction}}
|
||||
</div>
|
||||
@@ -36,11 +33,25 @@
|
||||
<div>{{frappe.utils.md_to_html(course.description)}}</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro BatchDetails(description) %}
|
||||
<div class="mt-5">
|
||||
<h3>About the Batch</h3>
|
||||
<div>
|
||||
{{ frappe.utils.md_to_html(description) }}
|
||||
</div>
|
||||
{% macro BatchDetails(batch) %}
|
||||
<h2>About the Batch</h2>
|
||||
|
||||
<div class="batch">
|
||||
<div class="batch-details">
|
||||
<div>Session every {{batch.sessions_on}}</div>
|
||||
<div>{{frappe.utils.format_time(batch.start_time, "short")}} -
|
||||
{{frappe.utils.format_time(batch.end_time, "short")}}</div>
|
||||
<div>Starting {{frappe.utils.format_date(batch.start_date, "medium")}}</div>
|
||||
<div class="course-type" style="color: #888; padding: 10px 0px;">mentors</div>
|
||||
|
||||
{% for m in batch.get_mentors() %}
|
||||
<div>
|
||||
{% if m.photo_url %}
|
||||
<img class="profile-photo" src="{{m.photo_url}}">
|
||||
{% endif %}
|
||||
<span class="instructor-title">{{m.full_name}}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -11,22 +11,23 @@
|
||||
<div class="course-header">
|
||||
<div class="course-type">course</div>
|
||||
<h1 id="course-title" data-course="{{course.name}}">{{course.title}}</h1>
|
||||
<div class="course-short-intro">{{ course.short_introduction }}</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-9 col-md-12">
|
||||
<div class="col-lg-8 col-md-12">
|
||||
<div class="course-details">
|
||||
{{ CourseVideo(course) }}
|
||||
|
||||
{{ CourseDescription(course) }}
|
||||
{{ BatchSection(course) }}
|
||||
{{ CourseOutline(course) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-3 col-md-12">
|
||||
<div class="col-lg-4 col-md-12">
|
||||
<div class="sidebar">
|
||||
{{ InstructorsSection(course.get_instructor()) }}
|
||||
</div>
|
||||
|
||||
<div class="sidebar">
|
||||
{{ MentorsSection(course.get_mentors(), course.is_mentor(frappe.session.user), course.name) }}
|
||||
</div>
|
||||
@@ -35,14 +36,7 @@
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% macro CourseDescription(course) %}
|
||||
<h2>Course Description</h2>
|
||||
|
||||
<div class="course-description">
|
||||
{{ course.short_introduction }}
|
||||
</div>
|
||||
|
||||
{% macro CourseVideo(course) %}
|
||||
{% if course.video_link %}
|
||||
<div class="preview-video">
|
||||
<iframe
|
||||
@@ -57,6 +51,14 @@
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro CourseDescription(course) %}
|
||||
<h2>Course Description</h2>
|
||||
|
||||
<div class="course-description">
|
||||
{{ course.short_introduction }}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro BatchSection(course) %}
|
||||
{% if course.is_mentor(frappe.session.user) %}
|
||||
{{ BatchSectionForMentors(course, course.get_batches(mentor=frappe.session.user)) }}
|
||||
@@ -76,9 +78,7 @@
|
||||
|
||||
{% for m in batch.get_mentors() %}
|
||||
<div>
|
||||
{% if m.photo_url %}
|
||||
<img class="profile-photo" src="{{m.photo_url}}">
|
||||
{% endif %}
|
||||
{{ widgets.Avatar(member=m, avatar_class="avatar-medium" ) }}
|
||||
<span class="instructor-title">{{m.full_name}}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
@@ -86,7 +86,7 @@
|
||||
<div class="cta">
|
||||
<div class="">
|
||||
{% if can_manage %}
|
||||
<button >Manage</button>
|
||||
<a href="/courses/{{course.name}}/{{batch.name}}/about" class="btn btn-secondary">Manage</a>
|
||||
{% else %}
|
||||
<button class="join-batch" data-batch="{{ batch.name | urlencode }}">Join this Batch</button>
|
||||
{% endif %}
|
||||
@@ -136,6 +136,6 @@
|
||||
<h2>Course Outline</h2>
|
||||
|
||||
{% for chapter in course.get_chapters() %}
|
||||
{{ widgets.ChapterTeaser(chapter=chapter)}}
|
||||
{{ widgets.ChapterTeaser(index=loop.index, chapter=chapter)}}
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
|
||||
@@ -7,12 +7,12 @@ def get_context(context):
|
||||
context.no_cache = 1
|
||||
|
||||
try:
|
||||
course_slug = frappe.form_dict["course"]
|
||||
course_name = frappe.form_dict["course"]
|
||||
except KeyError:
|
||||
frappe.local.flags.redirect_location = "/courses"
|
||||
raise frappe.Redirect
|
||||
|
||||
course = Course.find(course_slug)
|
||||
course = Course.find(course_name)
|
||||
if course is None:
|
||||
frappe.local.flags.redirect_location = "/courses"
|
||||
raise frappe.Redirect
|
||||
|
||||
@@ -26,11 +26,11 @@
|
||||
{% macro course_card(course) %}
|
||||
<div class="card mb-5 w-100">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><a href="/courses/{{course.slug}}">{{course.title}}</a></h5>
|
||||
<h5 class="card-title"><a href="/courses/{{course.name}}">{{course.title}}</a></h5>
|
||||
{% if course.description %}
|
||||
<p class="card-text">{{ frappe.utils.md_to_html(course.description[:250]) }}</p>
|
||||
{% endif %}
|
||||
<a href="/courses/{{course.slug}}" class="card-link">See more →</a>
|
||||
<a href="/courses/{{course.name}}" class="card-link">See more →</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{{ Sidebar(course, batch_code) }}
|
||||
{{ Sidebar(course.name, batch.name) }}
|
||||
<div class="container">
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,8 +1,21 @@
|
||||
import frappe
|
||||
from community.www.courses.utils import redirect_if_not_a_member
|
||||
from community.lms.models import Course
|
||||
|
||||
def get_context(context):
|
||||
context.no_cache = 1
|
||||
context.course = frappe.form_dict["course"]
|
||||
context.batch_code = frappe.form_dict["batch"]
|
||||
redirect_if_not_a_member(context.course, context.batch_code)
|
||||
|
||||
course_name = frappe.form_dict["course"]
|
||||
batch_name = frappe.form_dict["batch"]
|
||||
|
||||
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
|
||||
|
||||
@@ -14,12 +14,14 @@ def get_member_with_name(name):
|
||||
|
||||
def get_batch(code):
|
||||
try:
|
||||
return frappe.db.get_value("LMS Batch", {"code": code}, ["name", "description"], as_dict=True)
|
||||
print("get_batch", code)
|
||||
return frappe.db.get_value("LMS Batch", {"name": code}, ["name", "description"], as_dict=True)
|
||||
except frappe.DoesNotExistError:
|
||||
print("Error: notfound")
|
||||
return
|
||||
|
||||
def is_member_of_batch(batch_code):
|
||||
membership = frappe.get_all("LMS Batch Membership", {"batch": get_batch(batch_code).name, "member": get_member_with_email()})
|
||||
membership = frappe.get_all("LMS Batch Membership", {"batch": batch_code, "member": get_member_with_email()})
|
||||
if len(membership):
|
||||
return True
|
||||
return False
|
||||
@@ -29,9 +31,9 @@ def redirect_if_not_a_member(course,batch_code):
|
||||
frappe.local.flags.redirect_location = "/courses/" + course
|
||||
raise frappe.Redirect
|
||||
|
||||
def get_course(slug):
|
||||
def get_course(name):
|
||||
try:
|
||||
return frappe.get_doc("LMS Course", {"slug": slug})
|
||||
return frappe.get_doc("LMS Course", {"name": name})
|
||||
except frappe.DoesNotExistError:
|
||||
return
|
||||
|
||||
@@ -54,4 +56,4 @@ def get_batch_members(batch):
|
||||
if membership.member_type == "Mentor":
|
||||
member.is_mentor = True
|
||||
members.append(member)
|
||||
return members
|
||||
return members
|
||||
|
||||
@@ -182,8 +182,8 @@
|
||||
{% for course in courses %}
|
||||
<div class="dashboard__course">
|
||||
<div class="dashboard__courseHeader">
|
||||
<a class="text-decoration-none" target="_blank" href="/courses/{{course.slug}}">
|
||||
<h5 class="w-75">{{ course.name }}</h5>
|
||||
<a class="text-decoration-none" target="_blank" href="/courses/{{course.name}}">
|
||||
<h5 class="w-75">{{ course.title }}</h5>
|
||||
</a>
|
||||
{% if course.member_type %}
|
||||
<div class="dashboard__badge">
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
{% macro InstructorsSection(instructor) %}
|
||||
<h3>Instructor</h3>
|
||||
<div class="instructor">
|
||||
<div class="instructor-title">{{instructor.full_name}}</div>
|
||||
<div class="instructor-subtitle">Created {{instructor.get_course_count()}} courses</div>
|
||||
</div>
|
||||
<h3>Instructor</h3>
|
||||
<div class="instructor">
|
||||
<div class="instructor-title">{{instructor.full_name}}</div>
|
||||
<div class="instructor-subtitle">Created {{instructor.get_course_count()}} courses</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro MentorsSection(mentors, is_mentor, course_name) %}
|
||||
<h3>Mentors</h3>
|
||||
{% for m in mentors %}
|
||||
<div class="instructor">
|
||||
<div class="instructor-title">{{m.full_name}}</div>
|
||||
<div class="instructor-subtitle">Mentored {{m.get_batch_count()}} batches</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if not is_mentor %}
|
||||
<div id="mentor-request" class="notice">
|
||||
Interested to become a mentor?
|
||||
<h3>Mentors</h3>
|
||||
{% for m in mentors %}
|
||||
<div class="instructor">
|
||||
<div class="instructor-title">{{m.full_name}}</div>
|
||||
<div class="instructor-subtitle">Mentored {{m.get_batch_count()}} batches</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if not is_mentor %}
|
||||
<div id="mentor-request" class="notice">
|
||||
Interested to become a mentor?
|
||||
|
||||
<div><a id="apply-now" data-course="{{course_name | urlencode}}" href="">Apply Now!</a></div>
|
||||
</div>
|
||||
<div id="already-applied" class="notice hide">
|
||||
You've applied to become a mentor for this course. Your request is currently under review.
|
||||
<div><a id="apply-now" data-course="{{course_name | urlencode}}" href="">Apply Now!</a></div>
|
||||
</div>
|
||||
<div id="already-applied" class="notice hide">
|
||||
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 <a id="cancel-request" data-course="{{course_name | urlencode}}" href="">cancel your application</a>.
|
||||
</div>
|
||||
{% endif %}
|
||||
If you are not any more interested to mentor this course, you can <a id="cancel-request" data-course="{{course_name | urlencode}}" href="">cancel your application</a>.
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user