Compare commits

...

18 Commits

Author SHA1 Message Date
Anand Chitipothu
20b3ae7d76 fix: error in linking lessons on course page
The course was adding `{{ no such element: community.lms.doctype.lms_course.lms_course.LMSCourse object['query_parameter'] }}`
to the lesson links. Fixed it by setting query_parameter to "".
2021-06-23 10:27:01 +05:30
Anand Chitipothu
f303be4db5 fix: error in find_macros when the input is empty
Added a special case to handle this issue.
2021-06-22 18:12:31 +05:30
Anand Chitipothu
fc1c393f15 feat: allow a student to be mentor of another batch
This is a requirement for mon.school. The students are of the first
batch are now mentors of new batches.
2021-06-22 18:09:21 +05:30
Jannat Patel
5abfa35095 Merge pull request #132 from fossunited/learning-modes
feat: learning modes and batch switching
2021-06-22 12:23:46 +05:30
pateljannat
6c751cdf39 fix: test 2021-06-22 12:17:06 +05:30
pateljannat
2c570ea214 fix: added default value for arguements 2021-06-22 10:48:33 +05:30
pateljannat
ecfcc8a2f7 fix: redirects and urls 2021-06-22 10:45:07 +05:30
pateljannat
3384f974e5 fix: batch switch with query parameters 2021-06-22 10:11:21 +05:30
pateljannat
eb435261fe feat: learning modes 2021-06-18 18:31:10 +05:30
Jannat Patel
dc7eabefb9 Merge pull request #131 from fossunited/minor-fixes
fix: web form, progress ui, title non unique
2021-06-16 13:15:10 +05:30
pateljannat
fed4b5568b fix: web form, progress ui, title non unique 2021-06-16 13:04:45 +05:30
Jannat Patel
aa77c60abd Merge pull request #129 from fossunited/minor-fix
fix: minor issues
2021-06-15 18:46:33 +05:30
pateljannat
9c1506d3c8 fix: minor issues 2021-06-15 18:40:14 +05:30
Jannat Patel
e94c3f27ab Merge pull request #128 from fossunited/ui-fixes
fix: UI fixes
2021-06-15 13:19:03 +05:30
Anand Chitipothu
526ded784b Merge pull request #125 from fossunited/hotfix-exercise-image
fix: fixed error on saving exercises
2021-06-12 21:42:27 +05:30
Anand Chitipothu
6b5ddcd54a fix: fixed error on saving exercises
Removed the image generation when exercise is saved. The library used
for exercises has changed and generating the image doesn't work any
more.
2021-06-12 10:49:27 +05:30
Jannat Patel
c42247db42 Merge pull request #122 from fderyckel/patch-1
frappe wasn't imported
2021-06-10 20:47:05 +05:30
François de Ryckel
8f8d4901ff frappe wasn't imported
error with NameError: name 'frappe' is not defined
2021-06-10 18:04:40 +03:00
33 changed files with 201 additions and 205 deletions

View File

@@ -165,6 +165,7 @@ whitelist = [
"/add-a-new-batch", "/add-a-new-batch",
"/new-sign-up", "/new-sign-up",
"/message" "/message"
"/about"
] ]
whitelist_rules = [{"from_route": p, "to_route": p[1:]} for p in whitelist] whitelist_rules = [{"from_route": p, "to_route": p[1:]} for p in whitelist]

View File

@@ -29,13 +29,13 @@ def submit_solution(exercise, code):
return {"name": doc.name, "creation": doc.creation} return {"name": doc.name, "creation": doc.creation}
@frappe.whitelist() @frappe.whitelist()
def save_current_lesson(batch_name, lesson_name): def save_current_lesson(course_name, lesson_name):
"""Saves the current lesson for a student/mentor. """Saves the current lesson for a student/mentor.
""" """
name = frappe.get_value( name = frappe.get_value(
doctype="LMS Batch Membership", doctype="LMS Batch Membership",
filters={ filters={
"batch": batch_name, "course": course_name,
"member": frappe.session.user "member": frappe.session.user
}, },
fieldname="name") fieldname="name")

View File

@@ -3,12 +3,9 @@
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
from ..lms_sketch.livecode import livecode_to_svg # from ..lms_sketch.livecode import livecode_to_svg
class Exercise(Document): class Exercise(Document):
def before_save(self):
self.image = livecode_to_svg(None, self.answer)
def get_user_submission(self): def get_user_submission(self):
"""Returns the latest submission for this user. """Returns the latest submission for this user.
""" """
@@ -42,8 +39,6 @@ class Exercise(Document):
course = frappe.get_doc("LMS Course", self.course) course = frappe.get_doc("LMS Course", self.course)
batch = course.get_student_batch(user) batch = course.get_student_batch(user)
image = livecode_to_svg(None, code)
doc = frappe.get_doc( doc = frappe.get_doc(
doctype="Exercise Submission", doctype="Exercise Submission",
exercise=self.name, exercise=self.name,
@@ -51,7 +46,6 @@ class Exercise(Document):
course=self.course, course=self.course,
lesson=self.lesson, lesson=self.lesson,
batch=batch and batch.name, batch=batch and batch.name,
image=image,
solution=code) solution=code)
doc.insert(ignore_permissions=True) doc.insert(ignore_permissions=True)

View File

@@ -55,11 +55,11 @@ class Lesson(Document):
return return
@frappe.whitelist() @frappe.whitelist()
def save_progress(lesson, batch): def save_progress(lesson, course):
if not frappe.db.exists("LMS Batch Membership", if not frappe.db.exists("LMS Batch Membership",
{ {
"member": frappe.session.user, "member": frappe.session.user,
"batch": batch "course": course
}): }):
return return
if frappe.db.exists("LMS Course Progress", if frappe.db.exists("LMS Course Progress",

View File

@@ -33,8 +33,7 @@
{ {
"fieldname": "title", "fieldname": "title",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Title", "label": "Title"
"unique": 1
}, },
{ {
"fieldname": "description", "fieldname": "description",
@@ -120,7 +119,7 @@
"link_fieldname": "batch" "link_fieldname": "batch"
} }
], ],
"modified": "2021-05-26 16:43:57.399747", "modified": "2021-06-16 10:51:05.403726",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "LMS", "module": "LMS",
"name": "LMS Batch", "name": "LMS Batch",

View File

@@ -19,15 +19,7 @@ class LMSBatch(Document):
frappe.throw(_("You are not a mentor of the course {0}").format(course.title)) frappe.throw(_("You are not a mentor of the course {0}").format(course.title))
def after_insert(self): def after_insert(self):
create_membership(batch=self.name, member_type="Mentor") create_membership(batch=self.name, course=self.course, member_type="Mentor")
def get_mentors(self):
memberships = frappe.get_all(
"LMS Batch Membership",
{"batch": self.name, "member_type": "Mentor"},
["member"])
member_names = [m['member'] for m in memberships]
return find_all("User", name=["IN", member_names])
def is_member(self, email, member_type=None): def is_member(self, email, member_type=None):
"""Checks if a person is part of a batch. """Checks if a person is part of a batch.
@@ -43,16 +35,6 @@ class LMSBatch(Document):
filters['member_type'] = member_type filters['member_type'] = member_type
return frappe.db.exists("LMS Batch Membership", filters) return frappe.db.exists("LMS Batch Membership", filters)
def get_students(self):
"""Returns (email, full_name, username) of all the students of this batch as a list of dict.
"""
memberships = frappe.get_all(
"LMS Batch Membership",
{"batch": self.name, "member_type": "Student"},
["member"])
member_names = [m['member'] for m in memberships]
return find_all("User", name=["IN", member_names])
def get_messages(self): def get_messages(self):
messages = frappe.get_all("LMS Message", {"batch": self.name}, ["*"], order_by="creation") messages = frappe.get_all("LMS Message", {"batch": self.name}, ["*"], order_by="creation")
for message in messages: for message in messages:

View File

@@ -13,8 +13,7 @@
"course", "course",
"member_type", "member_type",
"role", "role",
"current_lesson", "current_lesson"
"is_current"
], ],
"fields": [ "fields": [
{ {
@@ -81,19 +80,11 @@
"fieldtype": "Data", "fieldtype": "Data",
"label": "Memeber Username", "label": "Memeber Username",
"read_only": 1 "read_only": 1
},
{
"default": "0",
"fieldname": "is_current",
"fieldtype": "Check",
"hidden": 1,
"label": "Is Currently Being Used",
"read_only": 1
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2021-06-14 10:24:35.425498", "modified": "2021-06-21 12:10:28.808803",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "LMS", "module": "LMS",
"name": "LMS Batch Membership", "name": "LMS Batch Membership",

View File

@@ -14,40 +14,53 @@ class LMSBatchMembership(Document):
self.validate_membership_in_different_batch_same_course() self.validate_membership_in_different_batch_same_course()
def validate_membership_in_same_batch(self): def validate_membership_in_same_batch(self):
filters={
"member": self.member,
"course": self.course,
"name": ["!=", self.name]
}
if self.batch:
filters["batch"] = self.batch
previous_membership = frappe.db.get_value("LMS Batch Membership", previous_membership = frappe.db.get_value("LMS Batch Membership",
filters={ filters,
"member": self.member,
"batch": self.batch,
"name": ["!=", self.name]
},
fieldname=["member_type","member"], fieldname=["member_type","member"],
as_dict=1) as_dict=1)
if previous_membership: if previous_membership:
member_name = frappe.db.get_value("User", self.member, "full_name") member_name = frappe.db.get_value("User", self.member, "full_name")
frappe.throw(_("{0} is already a {1} of {2}").format(member_name, previous_membership.member_type, self.batch)) course_title = frappe.db.get_value("LMS Course", self.course, "title")
frappe.throw(_("{0} is already a {1} of the course {2}").format(member_name, previous_membership.member_type, course_title))
def validate_membership_in_different_batch_same_course(self): def validate_membership_in_different_batch_same_course(self):
course = frappe.db.get_value("LMS Batch", self.batch, "course") """Ensures that a studnet is only part of one batch.
previous_membership = frappe.get_all("LMS Batch Membership", """
filters={ # nothing to worry if the member is not a student
"member": self.member, if self.member_type != "Student":
"name": ["!=", self.name] return
},
fields=["batch", "member_type", "name"]
)
for membership in previous_membership: course = frappe.db.get_value("LMS Batch", self.batch, "course")
batch_course = frappe.db.get_value("LMS Batch", membership.batch, "course") memberships = frappe.get_all(
if batch_course == course and (membership.member_type == "Student" or self.member_type == "Student"): "LMS Batch Membership",
member_name = frappe.db.get_value("User", self.member, "full_name") filters={
frappe.throw(_("{0} is already a {1} of {2} course through {3} batch").format(member_name, membership.member_type, course, membership.batch)) "member": self.member,
"name": ["!=", self.name],
"member_type": "Student",
"course": self.course
},
fields=["batch", "member_type", "name"]
)
if memberships:
membership = memberships[0]
member_name = frappe.db.get_value("User", self.member, "full_name")
frappe.throw(_("{0} is already a Student of {1} course through {2} batch").format(member_name, course, membership.batch))
@frappe.whitelist() @frappe.whitelist()
def create_membership(batch, member=None, member_type="Student", role="Member"): def create_membership(course, batch=None, member=None, member_type="Student", role="Member"):
frappe.get_doc({ frappe.get_doc({
"doctype": "LMS Batch Membership", "doctype": "LMS Batch Membership",
"batch": batch, "batch": batch,
"course": course,
"role": role, "role": role,
"member_type": member_type, "member_type": member_type,
"member": member or frappe.session.user "member": member or frappe.session.user
@@ -55,7 +68,7 @@ def create_membership(batch, member=None, member_type="Student", role="Member"):
return "OK" return "OK"
@frappe.whitelist() @frappe.whitelist()
def update_current_membership(batch, course, member=frappe.session.user): def update_current_membership(batch, course, member):
all_memberships = frappe.get_all("LMS Batch Membership", {"member": member, "course": course}) all_memberships = frappe.get_all("LMS Batch Membership", {"member": member, "course": course})
for membership in all_memberships: for membership in all_memberships:
frappe.db.set_value("LMS Batch Membership", membership.name, "is_current", 0) frappe.db.set_value("LMS Batch Membership", membership.name, "is_current", 0)

View File

@@ -22,6 +22,7 @@
"field_order": [ "field_order": [
"title", "title",
"is_published", "is_published",
"disable_self_learning",
"column_break_3", "column_break_3",
"short_code", "short_code",
"video_link", "video_link",
@@ -73,6 +74,12 @@
"fieldtype": "Small Text", "fieldtype": "Small Text",
"label": "Short Introduction", "label": "Short Introduction",
"reqd": 1 "reqd": 1
},
{
"default": "0",
"fieldname": "disable_self_learning",
"fieldtype": "Check",
"label": "Disable Self Learning"
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
@@ -99,7 +106,7 @@
"link_fieldname": "course" "link_fieldname": "course"
} }
], ],
"modified": "2021-06-01 04:36:45.696776", "modified": "2021-06-21 11:34:04.552376",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "LMS", "module": "LMS",
"name": "LMS Course", "name": "LMS Course",

View File

@@ -82,11 +82,11 @@ class LMSCourse(Document):
""" """
if not email: if not email:
return False return False
return frappe.db.exists({ return frappe.db.count("LMS Course Mentor Mapping",
"doctype": "LMS Course Mentor Mapping", {
"course": self.name, "course": self.name,
"mentor": email "mentor": email
}) })
def get_student_batch(self, email): def get_student_batch(self, email):
"""Returns the batch the given student is part of. """Returns the batch the given student is part of.
@@ -192,22 +192,53 @@ class LMSCourse(Document):
return return
return f"/courses/{self.name}/learn/{lesson_number}" return f"/courses/{self.name}/learn/{lesson_number}"
def get_current_batch(self, member=frappe.session.user): def get_membership(self, member, batch=None):
current_membership = frappe.get_all("LMS Batch Membership", {"member": member, "course": self.name, "is_current": 1}, pluck="batch") filters = {
print(current_membership, member, self.name) "member": member,
if len(current_membership): "course": self.name
return current_membership[0] }
print(frappe.db.get_value("LMS Batch Membership", {"member": member, "course": self.name}, "batch")) if batch:
return frappe.db.get_value("LMS Batch Membership", {"member": member, "course": self.name}, "batch") filters["batch"] = batch
return frappe.db.get_value("LMS Batch Membership", filters, ["name","batch", "current_lesson"], as_dict=True)
def get_all_memberships(self, member=frappe.session.user): def get_all_memberships(self, member=frappe.session.user):
print(member) all_memberships = frappe.get_all("LMS Batch Membership", {"member": member, "course": self.name}, ["batch"])
all_memberships = frappe.get_all("LMS Batch Membership", {"member": member, "course": self.name}, ["batch", "is_current"])
print(all_memberships)
for membership in all_memberships: for membership in all_memberships:
membership.batch_title = frappe.db.get_value("LMS Batch", membership.batch, "title") membership.batch_title = frappe.db.get_value("LMS Batch", membership.batch, "title")
print(all_memberships)
return all_memberships return all_memberships
def get_mentors(self, batch=None):
filters = {
"course": self.name,
"member_type": "Mentor"
}
if batch:
filters["batch"] = batch
memberships = frappe.get_all(
"LMS Batch Membership",
filters,
["member"])
member_names = [m['member'] for m in memberships]
return find_all("User", name=["IN", member_names])
def get_students(self, batch=None):
"""Returns (email, full_name, username) of all the students of this batch as a list of dict.
"""
filters = {
"course": self.name,
"member_type": "Student"
}
if batch:
filters["batch"] = batch
memberships = frappe.get_all(
"LMS Batch Membership",
filters,
["member"])
member_names = [m['member'] for m in memberships]
return find_all("User", name=["IN", member_names])
def get_outline(self): def get_outline(self):
return CourseOutline(self) return CourseOutline(self)

View File

@@ -26,7 +26,6 @@ class TestLMSCourse(unittest.TestCase):
course = self.new_course("Test Course") course = self.new_course("Test Course")
assert course.title == "Test Course" assert course.title == "Test Course"
assert course.name == "test-course" assert course.name == "test-course"
assert course.get_mentors() == []
def test_find_all(self): def test_find_all(self):
courses = LMSCourse.find_all() courses = LMSCourse.find_all()

View File

@@ -36,6 +36,8 @@ def find_macros(text):
('Exercise', 'four-circles') ('Exercise', 'four-circles')
] ]
""" """
if not text:
return []
macros = re.findall(MACRO_RE, text) macros = re.findall(MACRO_RE, text)
# remove the quotes around the argument # remove the quotes around the argument
return [(name, _remove_quotes(arg)) for name, arg in macros] return [(name, _remove_quotes(arg)) for name, arg in macros]

View File

@@ -19,7 +19,7 @@
"is_standard": 1, "is_standard": 1,
"login_required": 1, "login_required": 1,
"max_attachment_size": 0, "max_attachment_size": 0,
"modified": "2021-06-14 15:28:08.206622", "modified": "2021-06-15 18:49:50.530001",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "LMS", "module": "LMS",
"name": "add-a-new-batch", "name": "add-a-new-batch",
@@ -38,7 +38,7 @@
{ {
"allow_read_on_all_link_options": 0, "allow_read_on_all_link_options": 0,
"fieldname": "course", "fieldname": "course",
"fieldtype": "Link", "fieldtype": "Data",
"hidden": 1, "hidden": 1,
"label": "Course", "label": "Course",
"max_length": 0, "max_length": 0,

View File

@@ -4,73 +4,62 @@
{{ course.title }}</span> {% endif %} {{ course.title }}</span> {% endif %}
{% set all_memberships = course.get_all_memberships() %} {% set all_memberships = course.get_all_memberships() %}
{% if all_memberships | length > 1 %} {% if all_memberships | length > 1 %}
<a class="nav-link pull-right" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" <a class="nav-link pull-right" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="false"> aria-expanded="false">
Switch Batch Switch Batch
</a> </a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown"> <div class="dropdown-menu" aria-labelledby="navbarDropdown">
{% for membership in all_memberships %} {% for data in all_memberships %}
{% if not membership.is_current %} {% if data.batch != membership.batch %}
<a class="dropdown-item switch-batch" href="#" data-batch="{{ membership.batch | urlencode }}" data-course="{{ course.name | urlencode }}">{{ membership.batch_title }}</a> <a class="dropdown-item switch-batch" href="/courses/{{ course.name }}/home?batch={{ data.batch }}">{{ data.batch_title }}</a>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}
</div> </div>
{% if not batch %} {% if not membership %}
{% set display_class = "hide" %} {% set display_class = "hide" %}
{% else %} {% else %}
{% set display_class = "" %} {% set display_class = "" %}
{% endif %} {% endif %}
<ul class="nav nav-tabs mt-4"> <ul class="nav nav-tabs mt-4">
<li class="nav-item {{ display_class }}"> <li class="nav-item">
<a class="nav-link" id="home" href="/courses/{{course.name}}/home">Home</a> <a class="nav-link" id="home" href="/courses/{{course.name}}/home{{ course.query_parameter }}">Home</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" id="learn" href="/courses/{{course.name}}/learn">Lessons</a> {% set lesson_index = course.get_lesson_index(membership.current_lesson) if membership and membership.current_lesson else '1.1' %}
<a class="nav-link" id="learn"
href="{{ course.get_learn_url(lesson_index) }}{{ course.query_parameter }}">Lessons</a>
</li> </li>
<!-- <li class="nav-item"> <!-- <li class="nav-item">
<a class="nav-link" id="schedule" href="/courses/{{course.name}}/schedule">Schedule</a> <a class="nav-link" id="schedule" href="/courses/{{course.name}}/schedule">Schedule</a>
</li> --> </li> -->
<li class="nav-item {{ display_class }}"> <li class="nav-item {{ display_class }}">
<a class="nav-link" id="members" href="/courses/{{course.name}}/members">Members</a> <a class="nav-link" id="members" href="/courses/{{course.name}}/members{{ course.query_parameter }}">Members</a>
</li> </li>
<li class="nav-item {{ display_class }}"> <!-- <li class="nav-item {{ display_class }}">
<a class="nav-link" id="discussion" href="/courses/{{course.name}}/discuss">Discussion</a> <a class="nav-link" id="discussion" href="/courses/{{course.name}}/discuss">Discussion</a>
</li> </li> -->
<!-- <li class="nav-item"> <!-- <li class="nav-item">
<a class="nav-link" id="about" href="/courses/{{course.name}}/about">About</a> <a class="nav-link" id="about" href="/courses/{{course.name}}/about">About</a>
</li> --> </li> -->
{% if batch and batch.is_member(frappe.session.user, member_type="Mentor") %} {% if membership and membership.batch and course.is_mentor(frappe.session.user) %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" id="progress" href="/courses/{{course.name}}/progress">Progress</a> <a class="nav-link" id="progress" href="/courses/{{course.name}}/progress{{ course.query_parameter }}">Progress</a>
</li> </li>
{% endif %} {% endif %}
</ul> </ul>
{% block script %}
<script> <script>
frappe.ready(() => { frappe.ready(() => {
var selector = document.querySelector(`a[href="${decodeURIComponent(window.location.pathname)}"]`) var selector = document.querySelector(`a[href="${decodeURIComponent(window.location.pathname)}{{ course.query_parameter }}"]`)
if (selector) { if (selector) {
selector.classList.add('active'); selector.classList.add('active');
} }
else { else {
$("#learn").addClass('active') $("#learn").addClass('active')
} }
$(".switch-batch").click((e) => {
e.preventDefault();
var batch = decodeURIComponent($(e.currentTarget).attr("data-batch"));
var course = decodeURIComponent($(e.currentTarget).attr("data-course"));
frappe.call({
method: "community.lms.doctype.lms_batch_membership.lms_batch_membership.update_current_membership",
args: {
batch: batch,
course: course
},
callback: (data) => {
window.location.reload();
}
})
})
}) })
</script> </script>
{% endblock %}

View File

@@ -8,11 +8,10 @@
{% for lesson in chapter.get_lessons() %} {% for lesson in chapter.get_lessons() %}
<div class="lesson-teaser"> <div class="lesson-teaser">
<a {% if show_link or lesson.include_in_preview %} <a {% if show_link or lesson.include_in_preview %}
href="{{ course.get_learn_url(course.get_lesson_index(lesson.name)) }}" {% else %} href="" class="no-preview" href="{{ course.get_learn_url(course.get_lesson_index(lesson.name)) }}{{course.query_parameter}}" {% else %} href="" class="no-preview"
{% endif %} data-course="{{ course.name }}">{{ lesson.title }}</a> {% endif %} data-course="{{ course.name }}">{{ lesson.title }}</a>
{% if show_progress and not course.is_mentor(frappe.session.user) and lesson.get_progress() %} {% if show_progress and not course.is_mentor(frappe.session.user) and lesson.get_progress() %}
<a class="ml-5 badge p-1 {{ lesson.get_slugified_class() }}"> <img class="progress-image" <span class="ml-5 badge p-2 {{ lesson.get_slugified_class() }}"> {{ lesson.get_progress() }}</span>
src="/assets/community/images/Vector.png"> {{ lesson.get_progress() }}</a>
{% endif %} {% endif %}
</div> </div>
{% endfor %} {% endfor %}

View File

@@ -7,7 +7,7 @@
<div>Starting {{frappe.utils.format_date(batch.start_date, "medium")}}</div> <div>Starting {{frappe.utils.format_date(batch.start_date, "medium")}}</div>
<div class="course-type" style="color: #888; padding: 10px 0px;">mentors</div> <div class="course-type" style="color: #888; padding: 10px 0px;">mentors</div>
{% for m in batch.get_mentors() %} {% for m in course.get_mentors(batch.name) %}
<div> <div>
{{ widgets.Avatar(member=m, avatar_class="avatar-medium" ) }} {{ widgets.Avatar(member=m, avatar_class="avatar-medium" ) }}
<span class="instructor-title">{{m.full_name}}</span> <span class="instructor-title">{{m.full_name}}</span>
@@ -18,10 +18,10 @@
<div class="cta"> <div class="cta">
<div class=""> <div class="">
{% if can_manage %} {% if can_manage %}
<a href="" class="btn btn-primary manage-batch" data-batch="{{ batch.name | urlencode }}" <a href="/courses/{{ course.name }}/home?batch={{ batch.name }}" class="btn btn-primary manage-batch" data-batch="{{ batch.name | urlencode }}"
data-course="{{ course.name | urlencode }}">Manage</a> data-course="{{ course.name | urlencode }}">Manage</a>
{% elif can_join %} {% elif can_join %}
<button class="join-batch btn btn-primary" data-batch="{{ batch.name | urlencode }}" <button class="join-batch btn btn-secondary" data-batch="{{ batch.name | urlencode }}"
data-course="{{ course.name | urlencode }}">Join this Batch</button> data-course="{{ course.name | urlencode }}">Join this Batch</button>
{% endif %} {% endif %}
</div> </div>

View File

@@ -23,6 +23,7 @@
--cta-color: var(--c4); --cta-color: var(--c4);
--send-message: var(--c7); --send-message: var(--c7);
--received-message: var(--c8); --received-message: var(--c8);
--control-bg: var(--gray-100);
} }
body { body {

View File

@@ -11,7 +11,7 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
{{ widgets.BatchTabs(course=course, batch=batch) }} {{ widgets.BatchTabs(course=course, membership=membership) }}
<div class="messages-container mt-5"> <div class="messages-container mt-5">
{{ widgets.BatchHeader(batch_name=batch.title, member_count=member_count)}} {{ widgets.BatchHeader(batch_name=batch.title, member_count=member_count)}}
<ol class="messages"> <ol class="messages">

View File

@@ -4,5 +4,5 @@ from . import utils
def get_context(context): def get_context(context):
utils.get_common_context(context) utils.get_common_context(context)
context.messages = context.batch.get_messages() context.messages = context.batch.get_messages()
if not context.batch: if not context.membership:
utils.redirect_to_lesson(context.course) utils.redirect_to_lesson(context.course)

View File

@@ -8,15 +8,13 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
{% set invite_link = frappe.utils.get_url() + "/courses/" + course.name + "/join?batch=" + batch.name %}
<div class="container mt-5"> <div class="container mt-5">
{{ widgets.BatchTabs(course=course, batch=batch) }} {{ widgets.BatchTabs(course=course, membership=membership) }}
<!-- <div>
<h1 class="mt-5">{{ batch.title }}</h1>
</div> -->
<div class="course-details mt-5"> <div class="course-details mt-5">
{{ widgets.CourseOutline(course=course, batch=batch, show_link=True, show_progress=True) }} {{ widgets.CourseOutline(course=course, batch=batch, show_link=True, show_progress=True) }}
</div> </div>
{% if batch %}
<div class="w-25"> <div class="w-25">
<h3>Batch Schedule</h3> <h3>Batch Schedule</h3>
{{ widgets.RenderBatch(course=course, batch=batch) }} {{ widgets.RenderBatch(course=course, batch=batch) }}
@@ -28,8 +26,9 @@
{{ frappe.utils.md_to_html(batch.description) }} {{ frappe.utils.md_to_html(batch.description) }}
</div> </div>
{% endif %} {% endif %}
{% endif %}
{% if course.is_mentor(frappe.session.user) %} {% if course.is_mentor(frappe.session.user) %}
{% set invite_link = frappe.utils.get_url() + "/courses/" + course.name + "/join?batch=" + batch.name %}
<div class=""> <div class="">
<h3> Invite Members </h3> <h3> Invite Members </h3>
<a href="" class="" id="invite-link" data-link="{{ invite_link }}">Get Batch Invitation <a href="" class="" id="invite-link" data-link="{{ invite_link }}">Get Batch Invitation

View File

@@ -3,5 +3,3 @@ from . import utils
def get_context(context): def get_context(context):
utils.get_common_context(context) utils.get_common_context(context)
if not context.batch:
utils.redirect_to_lesson(context.course)

View File

@@ -51,7 +51,8 @@ frappe.ready(() => {
frappe.call({ frappe.call({
"method": "community.lms.doctype.lms_batch_membership.lms_batch_membership.create_membership", "method": "community.lms.doctype.lms_batch_membership.lms_batch_membership.create_membership",
"args": { "args": {
"batch": "{{ batch.name }}" "batch": {{ batch.name }},
"course": {{ batch.course }}
}, },
"callback": (data) => { "callback": (data) => {
if (data.message == "OK") { if (data.message == "OK") {
@@ -66,19 +67,6 @@ frappe.ready(() => {
} }
}) })
}) })
$("#batch-home").click((e) => {
frappe.call({
method: "community.lms.doctype.lms_batch_membership.lms_batch_membership.update_current_membership",
args: {
"batch": "{{ batch.name }}",
"course": "{{ batch.course}}"
},
callback: (data) => {
window.location.href = "/courses/{{ batch.course }}/home"
}
})
})
}) })
</script> </script>
{% endblock %} {% endblock %}

View File

@@ -13,7 +13,7 @@
<link rel="stylesheet" href="/assets/frappe/css/hljs-night-owl.css"> <link rel="stylesheet" href="/assets/frappe/css/hljs-night-owl.css">
{% for ext in page_extensions %} {% for ext in page_extensions %}
{{ ext.render_header() }} {{ ext.render_header() }}
{% endfor %} {% endfor %}
{% endblock %} {% endblock %}
@@ -22,17 +22,18 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
{{ widgets.BatchTabs(course=course, batch=batch) }} {{ widgets.BatchTabs(course=course, membership=membership) }}
<div class="lesson-page"> <div class="lesson-page">
<h2 class="title {% if course.is_mentor(frappe.session.user) %} is_mentor {% endif %}" data-name="{{ lesson.name }}" {% if batch %} data-batch="{{ batch.name }}" {% endif %}>{{ lesson.title }}</h2> <h2 class="title {% if course.is_mentor(frappe.session.user) %} is_mentor {% endif %}" data-lesson="{{ lesson.name }}"
data-course="{{ course.name }}" {% if membership%} data-membership="{{membership.name}}" {% endif %}>{{ lesson.title }}</h2>
{% if batch or lesson.include_in_preview %} {% if membership or lesson.include_in_preview %}
{{ lesson.render_html() }} {{ lesson.render_html() }}
{% else %} {% else %}
<div class="no-preview-message"> <div class="no-preview-message">
<span>This lesson is not available for Preview. Please join a batch to access the complete course.</span> <span>This lesson is not available for Preview. Please join the course to access this lesson.</span>
<a href="/courses/{{ course.name }}">Checkout Upcoming Batches</a> <a href="/courses/{{ course.name }}">Checkout Course Details.</a>
</div> </div>
{% endif %} {% endif %}
@@ -61,7 +62,6 @@
{%- block script %} {%- block script %}
{{ super() }} {{ super() }}
{% for ext in page_extensions %} {% for ext in page_extensions %}
{{ ext.render_footer() }} {{ ext.render_footer() }}
{% endfor %} {% endfor %}
{%- endblock %} {%- endblock %}

View File

@@ -1,17 +1,17 @@
frappe.ready(() => { frappe.ready(() => {
if ($(".title").attr("data-batch") && !$(".title").hasClass("is_mentor")) { if ($(".title").attr("data-membership") && !$(".title").hasClass("is_mentor")) {
frappe.call({ frappe.call({
method: "community.lms.doctype.lesson.lesson.save_progress", method: "community.lms.doctype.lesson.lesson.save_progress",
args: { args: {
lesson: $(".title").attr("data-name"), lesson: $(".title").attr("data-lesson"),
batch: $(".title").attr("data-batch") course: $(".title").attr("data-course")
} }
}) })
} }
if ($(".title").attr("data-batch")) { if ($(".title").attr("data-membership")) {
frappe.call("community.lms.api.save_current_lesson", { frappe.call("community.lms.api.save_current_lesson", {
"batch_name": $(".title").attr("data-batch"), course_name: $(".title").attr("data-course"),
"lesson_name": $(".title").attr("data-name") lesson_name: $(".title").attr("data-lesson")
}) })
} }
}) })

View File

@@ -18,7 +18,7 @@ def get_context(context):
index_ = get_lesson_index(context.course, context.batch, frappe.session.user) or "1.1" index_ = get_lesson_index(context.course, context.batch, frappe.session.user) or "1.1"
else: else:
index_ = "1.1" index_ = "1.1"
frappe.local.flags.redirect_location = context.course.get_learn_url(index_) frappe.local.flags.redirect_location = context.course.get_learn_url(index_) + context.course.query_parameter
raise frappe.Redirect raise frappe.Redirect
context.lesson = context.course.get_lesson(chapter_index, lesson_index) context.lesson = context.course.get_lesson(chapter_index, lesson_index)
@@ -30,8 +30,8 @@ def get_context(context):
next_ = outline.get_next(lesson_number) next_ = outline.get_next(lesson_number)
context.prev_chap = get_chapter_title(course_name, prev_) context.prev_chap = get_chapter_title(course_name, prev_)
context.next_chap = get_chapter_title(course_name, next_) context.next_chap = get_chapter_title(course_name, next_)
context.next_url = context.course.get_learn_url(next_) context.next_url = context.course.get_learn_url(next_) + context.course.query_parameter
context.prev_url = context.course.get_learn_url(prev_) context.prev_url = context.course.get_learn_url(prev_) + context.course.query_parameter
context.page_extensions = get_page_extensions() context.page_extensions = get_page_extensions()

View File

@@ -10,7 +10,7 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
{{ widgets.BatchTabs(course=course, batch=batch) }} {{ widgets.BatchTabs(course=course, membership=membership) }}
{{ MembersList(members)}} {{ MembersList(members)}}
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -3,5 +3,5 @@ from . import utils
def get_context(context): def get_context(context):
utils.get_common_context(context) utils.get_common_context(context)
if not context.batch: if not context.membership:
utils.redirect_to_lesson(context.course) utils.redirect_to_lesson(context.course)

View File

@@ -24,7 +24,7 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
{{ widgets.BatchTabs(course=course, batch=batch) }} {{ widgets.BatchTabs(course=course, membership=membership) }}
<div class="mentor-dashboard"> <div class="mentor-dashboard">
<h3>Batch Progress</h3> <h3>Batch Progress</h3>
{% for exercise in report.exercises %} {% for exercise in report.exercises %}

View File

@@ -17,7 +17,7 @@ def get_context(context):
class BatchReport: class BatchReport:
def __init__(self, course, batch): def __init__(self, course, batch):
self.submissions = get_submissions(batch) self.submissions = get_submissions(course, batch)
self.exercises = self.get_exercises(course.name) self.exercises = self.get_exercises(course.name)
self.submissions_by_exercise = defaultdict(list) self.submissions_by_exercise = defaultdict(list)
for s in self.submissions: for s in self.submissions:
@@ -29,8 +29,10 @@ class BatchReport:
def get_submissions_of_exercise(self, exercise_name): def get_submissions_of_exercise(self, exercise_name):
return self.submissions_by_exercise[exercise_name] return self.submissions_by_exercise[exercise_name]
def get_submissions(batch): def get_submissions(course, batch):
students = batch.get_students() students = course.get_students(batch.name)
if not len(students):
return []
students_map = {s.email: s for s in students} students_map = {s.email: s for s in students}
names, values = nparams("s", students_map.keys()) names, values = nparams("s", students_map.keys())
sql = """ sql = """

View File

@@ -5,26 +5,34 @@ def get_common_context(context):
context.no_cache = 1 context.no_cache = 1
course_name = frappe.form_dict["course"] course_name = frappe.form_dict["course"]
try:
batch_name = frappe.form_dict["batch"]
except KeyError:
batch_name = None
course = Course.find(course_name) course = Course.find(course_name)
if not course: if not course:
context.template = "www/404.html" context.template = "www/404.html"
return return
context.course = course
batch_name = course.get_current_batch() membership = course.get_membership(frappe.session.user, batch_name)
batch = course.get_batch(batch_name) if membership:
context.batch = batch context.membership = membership
if batch_name: batch = course.get_batch(membership.batch)
context.members = batch.get_mentors() + batch.get_students()
if batch:
context.batch = batch
context.members = course.get_mentors(membership.batch) + course.get_students(membership.batch)
context.member_count = len(context.members) context.member_count = len(context.members)
context.course.query_parameter = "?batch=" + membership.batch if membership and membership.batch else ""
context.course = course
context.livecode_url = get_livecode_url() context.livecode_url = get_livecode_url()
def get_livecode_url(): def get_livecode_url():
return frappe.db.get_single_value("LMS Settings", "livecode_url") return frappe.db.get_single_value("LMS Settings", "livecode_url")
def redirect_to_lesson(course, index_="1.1"): def redirect_to_lesson(course, index_="1.1"):
frappe.local.flags.redirect_location = course.get_learn_url(index_) frappe.local.flags.redirect_location = course.get_learn_url(index_) + course.query_parameter
raise frappe.Redirect raise frappe.Redirect

View File

@@ -12,7 +12,14 @@
<div class="mb-5"> <div class="mb-5">
<a class="anchor_style" href="/courses">Courses</a> / <span class="text-muted">{{ course.title }}</span> <a class="anchor_style" href="/courses">Courses</a> / <span class="text-muted">{{ course.title }}</span>
</div> </div>
<h2 id="course-title" data-course="{{course.name}}">{{course.title}}</h2> <div class="d-flex justify-content-between align-items-end">
<h2 id="course-title" data-course="{{course.name}}">{{course.title}}</h2>
{% if not course.disable_self_learning and not course.is_mentor(frappe.session.user) %}
<div>
<button class="btn btn-primary join-batch" data-course="{{ course.name | urlencode }}"> Start Learning </button>
</div>
{% endif %}
</div>
<div class="course-short-intro">{{ course.short_introduction }}</div> <div class="course-short-intro">{{ course.short_introduction }}</div>
</div> </div>
@@ -75,12 +82,12 @@
{% endfor %} {% endfor %}
</div> </div>
<a class="add-batch margin-bottom" href="/add-a-new-batch?new=1&course={{course.title}}&slug={{course.name}}">Add a new <a class="add-batch margin-bottom" href="/add-a-new-batch?new=1&course={{course.name}}">Add a new
batch</a> batch</a>
{% else %} {% else %}
<div class="mentor_message"> <div class="mentor_message">
<p> You are a mentor for this course. </p> <p> You are a mentor for this course. </p>
<a class="" href="/add-a-new-batch?new=1&course={{course.title}}&slug={{course.name}}">Create your first batch</a> <a class="" href="/add-a-new-batch?new=1&course={{course.name}}">Create your first batch</a>
</div> </div>
{% endif %} {% endif %}
{% endmacro %} {% endmacro %}
@@ -96,8 +103,8 @@
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
{% else %} {% else %}
<div class="mt-5 upcoming">There are no Upcoming Batches for this course currently.</div> <div class="mt-5 upcoming">There are no Upcoming Batches for this course currently.</div>
{% endif %} {% endif %}
</div> </div>
{% endmacro %} {% endmacro %}

View File

@@ -57,11 +57,13 @@ frappe.ready(() => {
window.location.href = `/login?redirect-to=/courses/${course}`; window.location.href = `/login?redirect-to=/courses/${course}`;
return; return;
} }
batch = decodeURIComponent($(e.currentTarget).attr("data-batch")) var batch = $(e.currentTarget).attr("data-batch");
batch = batch ? decodeURIComponent(batch) : "";
frappe.call({ frappe.call({
"method": "community.lms.doctype.lms_batch_membership.lms_batch_membership.create_membership", "method": "community.lms.doctype.lms_batch_membership.lms_batch_membership.create_membership",
"args": { "args": {
"batch": batch "batch": batch ? batch : "",
"course": course
}, },
"callback": (data) => { "callback": (data) => {
if (data.message == "OK") { if (data.message == "OK") {
@@ -73,20 +75,4 @@ frappe.ready(() => {
} }
}) })
}) })
$(".manage-batch").click((e) => {
e.preventDefault();
var batch = decodeURIComponent($(e.currentTarget).attr("data-batch"));
var course = decodeURIComponent($(e.currentTarget).attr("data-course"));
frappe.call({
method: "community.lms.doctype.lms_batch_membership.lms_batch_membership.update_current_membership",
args: {
batch: batch,
course: course
},
callback: (data) => {
window.location.href = `/courses/${course}/home`;
}
})
})
}) })

View File

@@ -16,9 +16,9 @@ def get_context(context):
raise frappe.Redirect raise frappe.Redirect
context.course = course context.course = course
context.course.query_parameter = ""
batch = course.get_student_batch(frappe.session.user) if not course.is_mentor(frappe.session.user):
if batch: batch = course.get_membership(frappe.session.user)
frappe.local.flags.redirect_location = f"/courses/{course.name}/learn" if batch:
raise frappe.Redirect frappe.local.flags.redirect_location = f"/courses/{course.name}/learn"
raise frappe.Redirect