Compare commits

...

26 Commits

Author SHA1 Message Date
Anand Chitipothu
94b3ccd3d9 feat: added a utililty to switch a student from one batch to another 2021-07-01 17:29:02 +05:30
Jannat Patel
4c3645f0d4 Merge pull request #133 from fossunited/mon-fixes-01
Various fixes from mon.school
2021-06-23 11:44:35 +05:30
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
pateljannat
5fa8bdd40c fix: invite request test, removed print statements and unused classes' 2021-06-15 13:09:48 +05:30
pateljannat
17f03aeee7 fix: join batch, removed code revision, redirects for other pages if batch missing 2021-06-15 13:01:57 +05:30
pateljannat
7840512a13 fix: ui, preview, progress, batches 2021-06-14 18:45:46 +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
pateljannat
f5f3c808d4 Merge branch 'main' of https://github.com/frappe/community into ui-fixes 2021-06-10 13:41:31 +05:30
pateljannat
1e3152e303 fix: ui 2021-06-10 13:41:11 +05:30
Jannat Patel
344661cf83 Merge pull request #121 from fossunited/lesson-markup
Lesson markup
2021-06-10 12:32:16 +05:30
55 changed files with 538 additions and 705 deletions

View File

@@ -3,8 +3,9 @@
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
# import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
from frappe import _
class CommunityProjectMember(Document): class CommunityProjectMember(Document):
def validate(self): def validate(self):

View File

@@ -136,15 +136,15 @@ primary_rules = [
{"from_route": "/hackathons/<hackathon>/<project>", "to_route": "hackathons/project"}, {"from_route": "/hackathons/<hackathon>/<project>", "to_route": "hackathons/project"},
{"from_route": "/dashboard", "to_route": ""}, {"from_route": "/dashboard", "to_route": ""},
{"from_route": "/add-a-new-batch", "to_route": "add-a-new-batch"}, {"from_route": "/add-a-new-batch", "to_route": "add-a-new-batch"},
{"from_route": "/courses/<course>/<batch>/home", "to_route": "batch/home"}, {"from_route": "/courses/<course>/home", "to_route": "batch/home"},
{"from_route": "/courses/<course>/<batch>/learn", "to_route": "batch/learn"}, {"from_route": "/courses/<course>/learn", "to_route": "batch/learn"},
{"from_route": "/courses/<course>/<batch>/learn/<int:chapter>.<int:lesson>", "to_route": "batch/learn"}, {"from_route": "/courses/<course>/learn/<int:chapter>.<int:lesson>", "to_route": "batch/learn"},
{"from_route": "/courses/<course>/<batch>/schedule", "to_route": "batch/schedule"}, {"from_route": "/courses/<course>/schedule", "to_route": "batch/schedule"},
{"from_route": "/courses/<course>/<batch>/members", "to_route": "batch/members"}, {"from_route": "/courses/<course>/members", "to_route": "batch/members"},
{"from_route": "/courses/<course>/<batch>/discuss", "to_route": "batch/discuss"}, {"from_route": "/courses/<course>/discuss", "to_route": "batch/discuss"},
{"from_route": "/courses/<course>/<batch>/about", "to_route": "batch/about"}, {"from_route": "/courses/<course>/about", "to_route": "batch/about"},
{"from_route": "/courses/<course>/<batch>/progress", "to_route": "batch/progress"}, {"from_route": "/courses/<course>/progress", "to_route": "batch/progress"},
{"from_route": "/courses/<course>/<batch>/join", "to_route": "batch/join"} {"from_route": "/courses/<course>/join", "to_route": "batch/join"}
] ]
# Any frappe default URL is blocked by profile-rules, add it here to unblock it # Any frappe default URL is blocked by profile-rules, add it here to unblock it
@@ -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

@@ -15,13 +15,6 @@ def autosave_section(section, code):
doc.insert() doc.insert()
return {"name": doc.name} return {"name": doc.name}
@frappe.whitelist()
def get_section(name):
"""Saves the code edited in one of the sections.
"""
doc = frappe.get_doc("LMS Section", name)
return doc and doc.as_dict()
@frappe.whitelist() @frappe.whitelist()
def submit_solution(exercise, code): def submit_solution(exercise, code):
"""Submits a solution. """Submits a solution.
@@ -36,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

@@ -1,8 +0,0 @@
// Copyright (c) 2021, FOSS United and contributors
// For license information, please see license.txt
frappe.ui.form.on('Code Revision', {
// refresh: function(frm) {
// }
});

View File

@@ -1,58 +0,0 @@
{
"actions": [],
"creation": "2021-04-07 00:26:28.806520",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"section",
"code",
"author"
],
"fields": [
{
"fieldname": "section",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Section",
"options": "LMS Section"
},
{
"fieldname": "code",
"fieldtype": "Code",
"label": "Code"
},
{
"fieldname": "author",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Author",
"options": "User"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-04-14 11:26:19.628317",
"modified_by": "Administrator",
"module": "LMS",
"name": "Code Revision",
"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",
"title_field": "section",
"track_changes": 1
}

View File

@@ -1,10 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021, FOSS United and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class CodeRevision(Document):
pass

View File

@@ -1,10 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021, FOSS United and Contributors
# See license.txt
from __future__ import unicode_literals
# import frappe
import unittest
class TestCodeRevision(unittest.TestCase):
pass

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

@@ -58,7 +58,8 @@ def create_invite_request(invite_email):
frappe.get_doc({ frappe.get_doc({
"doctype": "Invite Request", "doctype": "Invite Request",
"invite_email": invite_email "invite_email": invite_email,
"status": "Approved"
}).save(ignore_permissions=True) }).save(ignore_permissions=True)
return "OK" return "OK"

View File

@@ -18,7 +18,7 @@ class TestInviteRequest(unittest.TestCase):
filters={"invite_email": "test_invite@example.com"}, filters={"invite_email": "test_invite@example.com"},
fieldname=["invite_email", "status", "signup_email"], fieldname=["invite_email", "status", "signup_email"],
as_dict=True) as_dict=True)
self.assertEqual(invite.status, "Pending") self.assertEqual(invite.status, "Approved")
self.assertEqual(invite.signup_email, None) self.assertEqual(invite.signup_email, None)
def test_create_invite_request_update(self): def test_create_invite_request_update(self):

View File

@@ -8,11 +8,13 @@
"field_order": [ "field_order": [
"chapter", "chapter",
"lesson_type", "lesson_type",
"include_in_preview",
"column_break_4",
"title", "title",
"index_", "index_",
"index_label", "index_label",
"body", "section_break_6",
"sections" "body"
], ],
"fields": [ "fields": [
{ {
@@ -47,23 +49,31 @@
"fieldtype": "Markdown Editor", "fieldtype": "Markdown Editor",
"label": "Body" "label": "Body"
}, },
{
"fieldname": "sections",
"fieldtype": "Table",
"label": "Sections",
"options": "LMS Section"
},
{ {
"fieldname": "index_label", "fieldname": "index_label",
"fieldtype": "Data", "fieldtype": "Data",
"in_list_view": 1, "in_list_view": 1,
"label": "Index Label", "label": "Index Label",
"read_only": 1 "read_only": 1
},
{
"default": "0",
"fieldname": "include_in_preview",
"fieldtype": "Check",
"label": "Include In Preview"
},
{
"fieldname": "section_break_6",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2021-06-01 05:30:48.127494", "modified": "2021-06-11 19:03:23.138165",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "LMS", "module": "LMS",
"name": "Lesson", "name": "Lesson",

View File

@@ -5,7 +5,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
from ...section_parser import SectionParser
from ...md import markdown_to_html, find_macros from ...md import markdown_to_html, find_macros
class Lesson(Document): class Lesson(Document):
@@ -39,9 +38,6 @@ class Lesson(Document):
def render_html(self): def render_html(self):
return markdown_to_html(self.body) return markdown_to_html(self.body)
def get_sections(self):
return sorted(self.get('sections'), key=lambda s: s.index)
def get_exercises(self): def get_exercises(self):
if not self.body: if not self.body:
return [] return []
@@ -50,30 +46,6 @@ class Lesson(Document):
exercises = [value for name, value in macros if name == "Exercise"] exercises = [value for name, value in macros if name == "Exercise"]
return [frappe.get_doc("Exercise", name) for name in exercises] return [frappe.get_doc("Exercise", name) for name in exercises]
def make_lms_section(self, index, section):
s = frappe.new_doc('LMS Section', parent_doc=self, parentfield='sections')
s.type = section.type
s.id = section.id
s.label = section.label
s.contents = section.contents
s.index = index
return s
def get_next(self):
"""Returns the number for the next lesson.
The return value would be like 1.2, 2.1 etc.
It will be None if there is no next lesson.
"""
def get_prev(self):
"""Returns the number for the prev lesson.
The return value would be like 1.2, 2.1 etc.
It will be None if there is no next lesson.
"""
def get_progress(self): def get_progress(self):
return frappe.db.get_value("LMS Course Progress", {"lesson": self.name, "owner": frappe.session.user}, "status") return frappe.db.get_value("LMS Course Progress", {"lesson": self.name, "owner": frappe.session.user}, "status")
@@ -83,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",
@@ -98,11 +70,7 @@ def save_progress(lesson, batch):
return return
lesson_details = frappe.get_doc("Lesson", lesson) lesson_details = frappe.get_doc("Lesson", lesson)
dynamic_content = frappe.db.count("LMS Section", dynamic_content = find_macros(lesson_details.body)
filters={
"type": ["not in", ["example", "text"]],
"parent": lesson_details.name
})
status = "Complete" status = "Complete"
if dynamic_content: if dynamic_content:
@@ -121,12 +89,11 @@ def update_progress(lesson):
if frappe.db.exists("LMS Course Progress", {"lesson": lesson, "owner": user}): if frappe.db.exists("LMS Course Progress", {"lesson": lesson, "owner": user}):
course_progress = frappe.get_doc("LMS Course Progress", {"lesson": lesson, "owner": user}) course_progress = frappe.get_doc("LMS Course Progress", {"lesson": lesson, "owner": user})
course_progress.status = "Complete" course_progress.status = "Complete"
course_progress.save() course_progress.save(ignore_permissions=True)
def all_dynamic_content_submitted(lesson, user): def all_dynamic_content_submitted(lesson, user):
exercise_names = frappe.get_list("Exercise", {"lesson": lesson}, ["name"], pluck="name") exercise_names = frappe.get_list("Exercise", {"lesson": lesson}, pluck="name", ignore_permissions=True)
all_exercises_submitted = False all_exercises_submitted = False
print(exercise_names)
query = { query = {
"exercise": ["in", exercise_names], "exercise": ["in", exercise_names],
"owner": user "owner": user

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:
@@ -80,11 +62,6 @@ class LMSBatch(Document):
membership = self.get_membership(user) membership = self.get_membership(user)
return membership and membership.current_lesson return membership and membership.current_lesson
def get_learn_url(self, lesson_number):
if not lesson_number:
return
return f"/courses/{self.course}/{self.name}/learn/{lesson_number}"
@frappe.whitelist() @frappe.whitelist()
def save_message(message, batch): def save_message(message, batch):
doc = frappe.get_doc({ doc = frappe.get_doc({
@@ -94,3 +71,38 @@ def save_message(message, batch):
"message": message "message": message
}) })
doc.save(ignore_permissions=True) doc.save(ignore_permissions=True)
def switch_batch(course_name, email, batch_name):
"""Switches the user from the current batch of the course to a new batch.
"""
membership = frappe.get_last_doc(
"LMS Batch Membership",
filters={"course": course_name, "member": email})
batch = frappe.get_doc("LMS Batch", batch_name)
if not batch:
raise ValueError(f"Invalid Batch: {batch_name}")
if batch.course != course_name:
raise ValueError("Can not switch batches across courses")
if batch.is_member(email):
print(f"{email} is already a member of {batch.title}")
return
old_batch = frappe.get_doc("LMS Batch", membership.batch)
print("updating membership", membership.name)
membership.batch = batch_name
membership.save()
# update exercise submissions
filters = {
"owner": email,
"batch": old_batch.name
}
for name in frappe.db.get_all("Exercise Submission", filters=filters, pluck='name'):
doc = frappe.get_doc("Exercise Submission", name)
print("updating exercise submission", name)
doc.batch = batch_name
doc.save()

View File

@@ -84,7 +84,7 @@
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2021-05-24 12:40:57.125694", "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,42 +14,65 @@ 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):
previous_membership = frappe.db.get_value("LMS Batch Membership",
filters={ filters={
"member": self.member, "member": self.member,
"batch": self.batch, "course": self.course,
"name": ["!=", self.name] "name": ["!=", self.name]
}, }
if self.batch:
filters["batch"] = self.batch
previous_membership = frappe.db.get_value("LMS Batch Membership",
filters,
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):
"""Ensures that a studnet is only part of one batch.
"""
# nothing to worry if the member is not a student
if self.member_type != "Student":
return
course = frappe.db.get_value("LMS Batch", self.batch, "course") course = frappe.db.get_value("LMS Batch", self.batch, "course")
previous_membership = frappe.get_all("LMS Batch Membership", memberships = frappe.get_all(
"LMS Batch Membership",
filters={ filters={
"member": self.member, "member": self.member,
"name": ["!=", self.name] "name": ["!=", self.name],
"member_type": "Student",
"course": self.course
}, },
fields=["batch", "member_type", "name"] fields=["batch", "member_type", "name"]
) )
for membership in previous_membership: if memberships:
batch_course = frappe.db.get_value("LMS Batch", membership.batch, "course") membership = memberships[0]
if batch_course == course and (membership.member_type == "Student" or self.member_type == "Student"):
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} course through {3} batch").format(member_name, membership.member_type, course, membership.batch)) 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
}).save(ignore_permissions=True) }).save(ignore_permissions=True)
return "OK" return "OK"
@frappe.whitelist()
def update_current_membership(batch, course, member):
all_memberships = frappe.get_all("LMS Batch Membership", {"member": member, "course": course})
for membership in all_memberships:
frappe.db.set_value("LMS Batch Membership", membership.name, "is_current", 0)
current_membership = frappe.get_all("LMS Batch Membership", {"batch": batch, "member": member})
if len(current_membership):
frappe.db.set_value("LMS Batch Membership", current_membership[0].name, "is_current", 1)

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

@@ -8,6 +8,7 @@ from frappe.model.document import Document
import json import json
from ...utils import slugify from ...utils import slugify
from community.query import find, find_all from community.query import find, find_all
from frappe.utils import flt
class LMSCourse(Document): class LMSCourse(Document):
@staticmethod @staticmethod
@@ -81,8 +82,8 @@ 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
}) })
@@ -112,7 +113,7 @@ class LMSCourse(Document):
"""Returns all chapters of this course. """Returns all chapters of this course.
""" """
# TODO: chapters should have a way to specify the order # TODO: chapters should have a way to specify the order
return find_all("Chapter", course=self.name, order_by="creation") return find_all("Chapter", course=self.name, order_by="index_")
def get_batch(self, batch_name): def get_batch(self, batch_name):
return find("LMS Batch", name=batch_name, course=self.name) return find("LMS Batch", name=batch_name, course=self.name)
@@ -186,6 +187,57 @@ class LMSCourse(Document):
exercise.save() exercise.save()
i += 1 i += 1
def get_learn_url(self, lesson_number):
if not lesson_number:
return
return f"/courses/{self.name}/learn/{lesson_number}"
def get_membership(self, member, batch=None):
filters = {
"member": member,
"course": self.name
}
if 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):
all_memberships = frappe.get_all("LMS Batch Membership", {"member": member, "course": self.name}, ["batch"])
for membership in all_memberships:
membership.batch_title = frappe.db.get_value("LMS Batch", membership.batch, "title")
print(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)
@@ -197,6 +249,7 @@ class CourseOutline:
self.lessons = self.get_lessons() self.lessons = self.get_lessons()
def get_next(self, current): def get_next(self, current):
current = flt(current)
numbers = sorted(lesson['number'] for lesson in self.lessons) numbers = sorted(lesson['number'] for lesson in self.lessons)
try: try:
index = numbers.index(current) index = numbers.index(current)
@@ -205,6 +258,7 @@ class CourseOutline:
return None return None
def get_prev(self, current): def get_prev(self, current):
current = flt(current)
numbers = sorted(lesson['number'] for lesson in self.lessons) numbers = sorted(lesson['number'] for lesson in self.lessons)
try: try:
index = numbers.index(current) index = numbers.index(current)
@@ -228,7 +282,7 @@ class CourseOutline:
chapter_numbers = {c['name']: c['index_'] for c in self.chapters} chapter_numbers = {c['name']: c['index_'] for c in self.chapters}
for lesson in lessons: for lesson in lessons:
lesson['number'] = "{}.{}".format(chapter_numbers[lesson['chapter']], lesson['index_']) lesson['number'] = flt("{}.{}".format(chapter_numbers[lesson['chapter']], lesson['index_']))
return lessons return lessons
@frappe.whitelist() @frappe.whitelist()

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

@@ -1,66 +0,0 @@
{
"actions": [],
"creation": "2021-03-05 15:10:53.906006",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"label",
"type",
"contents",
"code",
"attrs",
"index",
"id"
],
"fields": [
{
"fieldname": "label",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Label"
},
{
"fieldname": "type",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Type"
},
{
"fieldname": "contents",
"fieldtype": "Markdown Editor",
"label": "Contents"
},
{
"fieldname": "code",
"fieldtype": "Code",
"label": "Code"
},
{
"fieldname": "attrs",
"fieldtype": "Long Text",
"label": "attrs"
},
{
"fieldname": "index",
"fieldtype": "Int",
"label": "Index"
},
{
"fieldname": "id",
"fieldtype": "Data",
"label": "id"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-05-19 18:55:26.019625",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Section",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -1,33 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021, FOSS United and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
class LMSSection(Document):
def __repr__(self):
return f"<LMSSection {self.label!r}>"
def get_exercise(self):
if self.type == "exercise":
return frappe.get_doc("Exercise", self.id)
def get_latest_code_for_user(self):
"""Returns the latest code for the logged in user.
"""
if not frappe.session.user or frappe.session.user == "Guest":
return self.contents
result = frappe.get_all('Code Revision',
fields=["code"],
filters={
"author": frappe.session.user,
"section": self.name
},
order_by="creation desc",
page_length=1)
if result:
return result[0]['code']
else:
return self.contents

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

@@ -2,4 +2,4 @@
""" """
from .doctype.lms_course.lms_course import LMSCourse as Course from .doctype.lms_course.lms_course import LMSCourse as Course
from .doctype.lms_sketch.lms_sketch import LMSSketch as Sketch from .doctype.lms_sketch.lms_sketch import LMSSketch as Sketch
from .doctype.lms_batch_membership.lms_batch_membership import LMSBatchMembership as Membership

View File

@@ -1,84 +0,0 @@
"""Utility to split the text in the topic into multiple sections.
{{ section(type="example", id="foo") }}
circle(100, 100, 50)
{{ end }}
"""
from __future__ import annotations
from dataclasses import dataclass
import re
from typing import List, Tuple, Dict, Iterator
RE_SECTION = re.compile(r"^\{\{\s(\w+)\s*(?:\((.*)\))?\s*\}\}\s*")
class SectionParser:
def parse(self, text: str) -> Iterator[Section]:
"""Parses given text into sections and return an iterator over sections.
"""
lines = text.splitlines()
marked_lines = self.parse_lines(lines)
return self.group_sections(marked_lines)
def parse_lines(self, lines: List[str]) -> List[Tuple[str, str, str]]:
for line in lines:
m = RE_SECTION.match(line)
if m:
yield m.group(1), self.parse_attrs(m.group(2)), None
else:
yield None, None, line
def parse_attrs(self, attrs_str: str) -> Dict[str, str]:
# XXX-Anand: Hack
code = "dict({})".format(attrs_str or "")
return eval(code)
def group_sections(self, marked_lines) -> Iterator[Section]:
index = 0
def make_section(type='text', id=None, label=None, **attrs):
nonlocal index
index += 1
id = id or f"section-{index}"
label = label or id
return Section(
type=type,
id=id,
label=label,
attrs=attrs)
section = make_section("text")
for mark, attrs, line in marked_lines:
if not mark:
section.append(line)
continue
yield section
if mark == 'end':
section = make_section(type='text')
else:
section = make_section(**attrs)
yield section
@dataclass
class Section:
"""One section of the Topic.
"""
type: str
id: str
label: str
contents: str = ""
attrs: dict = None
def append(self, line):
if not line.endswith("\n"):
line = line + "\n"
self.contents += line
def __repr__(self):
attrs = dict(type=self.type, id=self.id, label=self.label, **self.attrs)
attrs_str = ", ".join(f'{k}="{v}"' for k, v in attrs.items())
return f'<Section({attrs_str})>'

View File

@@ -11,7 +11,7 @@
"apply_document_permissions": 0, "apply_document_permissions": 0,
"button_label": "Save", "button_label": "Save",
"creation": "2021-04-20 11:37:49.135114", "creation": "2021-04-20 11:37:49.135114",
"custom_css": ".datepicker.active {\n background-color: white;\n}", "custom_css": ".datepicker.active {\n background-color: white;\n}\n\n[data-doctype=\"Web Form\"] {\n max-width: 720px;\n margin: 6rem auto;\n}",
"doc_type": "LMS Batch", "doc_type": "LMS Batch",
"docstatus": 0, "docstatus": 0,
"doctype": "Web Form", "doctype": "Web Form",
@@ -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-02 15:52:06.383260", "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

@@ -1,3 +0,0 @@
frappe.ready(function() {
// bind events here
})

View File

@@ -1,48 +0,0 @@
{
"accept_payment": 0,
"allow_comments": 0,
"allow_delete": 0,
"allow_edit": 0,
"allow_incomplete": 0,
"allow_multiple": 0,
"allow_print": 0,
"amount": 0.0,
"amount_based_on_field": 0,
"apply_document_permissions": 0,
"button_label": "Save",
"creation": "2021-04-15 13:32:14.171328",
"doc_type": "LMS Batch Membership",
"docstatus": 0,
"doctype": "Web Form",
"idx": 0,
"is_standard": 1,
"login_required": 1,
"max_attachment_size": 0,
"modified": "2021-04-15 13:32:14.171328",
"modified_by": "Administrator",
"module": "LMS",
"name": "join-a-batch",
"owner": "Administrator",
"payment_button_label": "Buy Now",
"published": 1,
"route": "join-a-batch",
"route_to_success_link": 0,
"show_attachments": 0,
"show_in_grid": 0,
"show_sidebar": 0,
"sidebar_items": [],
"success_url": "/join-a-batch",
"title": "Join a Batch",
"web_form_fields": [
{
"allow_read_on_all_link_options": 0,
"fieldtype": "Attach",
"hidden": 0,
"max_length": 0,
"max_value": 0,
"read_only": 0,
"reqd": 0,
"show_in_filter": 0
}
]
}

View File

@@ -1,7 +0,0 @@
from __future__ import unicode_literals
import frappe
def get_context(context):
# do your magic here
pass

View File

@@ -1,34 +1,59 @@
<div class="mt-5"> <div class="mt-5">
<a class="anchor_style" href="/courses">Courses</a> /{% if course.is_mentor(frappe.session.user) %} <a class="anchor_style" href="/courses/{{ course.name }}"> {{ course.title }}</a> {% else %} <span class="text-muted"> {{ course.title }}</span> {% endif %} <a class="anchor_style" href="/courses">Courses</a> /{% if course.is_mentor(frappe.session.user) %} <a
class="anchor_style" href="/courses/{{ course.name }}"> {{ course.title }}</a> {% else %} <span class="text-muted">
{{ course.title }}</span> {% endif %}
{% set all_memberships = course.get_all_memberships() %}
{% if all_memberships | length > 1 %}
<a class="nav-link pull-right" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="false">
Switch Batch
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
{% for data in all_memberships %}
{% if data.batch != membership.batch %}
<a class="dropdown-item switch-batch" href="/courses/{{ course.name }}/home?batch={{ data.batch }}">{{ data.batch_title }}</a>
{% endif %}
{% endfor %}
</div> </div>
{% endif %}
</div>
{% if not membership %}
{% set display_class = "hide" %}
{% else %}
{% set display_class = "" %}
{% endif %}
<ul class="nav nav-tabs mt-4"> <ul class="nav nav-tabs mt-4">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" id="home" href="/courses/{{course.name}}/{{batch.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}}/{{batch.name}}/learn">Learn</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}}/{{batch.name}}/schedule">Schedule</a> <a class="nav-link" id="schedule" href="/courses/{{course.name}}/schedule">Schedule</a>
</li> --> </li> -->
<li class="nav-item"> <li class="nav-item {{ display_class }}">
<a class="nav-link" id="members" href="/courses/{{course.name}}/{{batch.name}}/members">Members</a> <a class="nav-link" id="members" href="/courses/{{course.name}}/members{{ course.query_parameter }}">Members</a>
</li>
<li class="nav-item">
<a class="nav-link" id="discussion" href="/courses/{{course.name}}/{{batch.name}}/discuss">Discussion</a>
</li> </li>
<!-- <li class="nav-item {{ display_class }}">
<a class="nav-link" id="discussion" href="/courses/{{course.name}}/discuss">Discussion</a>
</li> -->
<!-- <li class="nav-item"> <!-- <li class="nav-item">
<a class="nav-link" id="about" href="/courses/{{course.name}}/{{batch.name}}/about">About</a> <a class="nav-link" id="about" href="/courses/{{course.name}}/about">About</a>
</li> --> </li> -->
{% if 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}}/{{batch.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');
} }
@@ -37,3 +62,4 @@
} }
}) })
</script> </script>
{% endblock %}

View File

@@ -1,18 +1,53 @@
<div class="chapter-teaser"> <div class="chapter-teaser">
<div class="teaser-body"> <div class="teaser-body">
<h3 class="chapter-title"><span class="mr-1">{{index}}.</span> {{ chapter.title }}</h3> <div class="chapter-title mb-5 font-weight-bold"><span class="mr-1">{{index}}.</span> {{ chapter.title }}</div>
<div class="chapter-description"> <div class="chapter-description">
{{ chapter.description or "" }} {{ chapter.description or "" }}
</div> </div>
<div class="chapter-lessons"> <div class="chapter-lessons">
{% for lesson in chapter.get_lessons() %} {% for lesson in chapter.get_lessons() %}
<div class="lesson-teaser"> <div class="lesson-teaser">
<a {% if show_link %} class="anchor_style" href="{{ batch.get_learn_url(course.get_lesson_index(lesson.name)) }}" {% endif %}>{{ lesson.title }}</a> <a {% if show_link or lesson.include_in_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>
{% 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="pull-right badge p-1 {{ lesson.get_slugified_class() }}"> <img class="progress-image" src="/assets/community/images/Vector.png"> {{ lesson.get_progress() }}</a> <span class="ml-5 badge p-2 {{ lesson.get_slugified_class() }}"> {{ lesson.get_progress() }}</span>
{% endif %} {% endif %}
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
</div> </div>
<script>
frappe.ready(() => {
var d;
$(".no-preview").click((e) => {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
var message = __("Please enroll for this course to access the lesson.");
var label = __("Checkout Upcoming Batches");
var action = "checkout_upcoming_batches";
d = frappe.msgprint({
title: __("This lesson is not available for preview!"),
message: message,
primary_action: {
"label": label,
"client_action": action,
}
});
})
window.redirect_to_login = () => {
window.location.href = `/login?redirect-to=/courses/${$(".no-preview").attr("data-course")}`
}
window.checkout_upcoming_batches = () => {
if ($(".upcoming").length > 0) {
$('html,body').animate({ scrollTop: $(".upcoming").offset().top }, 300);
}
frappe.hide_msgprint();
}
})
</script>

View File

@@ -1,5 +1,7 @@
<h2>Course Outline</h2> <div class="mt-5">
<h3> Course Outline </h3>
{% for chapter in course.get_chapters() %} {% for chapter in course.get_chapters() %}
{{ widgets.ChapterTeaser(index=loop.index, chapter=chapter, course=course, batch=batch, show_link=show_link, show_progress=show_progress)}} {{ widgets.ChapterTeaser(index=loop.index, chapter=chapter, course=course, batch=batch, show_link=show_link, show_progress=show_progress)}}
{% endfor %} {% endfor %}
</div>

View File

@@ -1,5 +1,6 @@
<h3>Instructor</h3>
<div class="instructor"> <div class="instructor">
<div class="instructor-title">{{instructor.full_name}}</div> {{ widgets.Avatar(member=instructor, avatar_class="avatar-medium") }}
<div class="instructor-subtitle">Created {{instructor.get_course_count()}} courses</div> <a class="ml-1 instructor-title" href="/{{instructor.username}}">{{ instructor.full_name }}</a>
<div class="instructor-subtitle">Course Creator</div>
<!-- <div class="instructor-subtitle">Created {{instructor.get_course_count()}} courses</div> -->
</div> </div>

View File

@@ -1,13 +1,13 @@
<div class="batch"> <div class="batch">
<div class="batch-details"> <div class="batch-details">
<div>Session every {{batch.sessions_on}}</div> <div class="">Session every {{batch.sessions_on}}</div>
<div>{{frappe.utils.format_time(batch.start_time, "short")}} - <div>{{frappe.utils.format_time(batch.start_time, "short")}} -
{{frappe.utils.format_time(batch.end_time, "short")}} {{frappe.utils.format_time(batch.end_time, "short")}}
</div> </div>
<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,9 +18,10 @@
<div class="cta"> <div class="cta">
<div class=""> <div class="">
{% if can_manage %} {% if can_manage %}
<a href="/courses/{{course.name}}/{{batch.name}}/home" class="btn btn-primary">Manage</a> <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>
{% 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 {
@@ -82,6 +83,7 @@ body {
border-radius: 10px; border-radius: 10px;
margin: 10px 0px; margin: 10px 0px;
background: white; background: white;
box-shadow: 0px 5px 10px rgb(0 0 0 / 10%);
border: 1px solid #ddc; border: 1px solid #ddc;
} }
@@ -156,19 +158,6 @@ img.profile-photo {
line-height: 51px; line-height: 51px;
} }
.anchor_style {
color: inherit;
}
a:hover {
text-decoration: none;
color: inherit;
}
.anchor_style:hover {
text-decoration: underline
}
section { section {
padding: 5rem 0 5rem 0; padding: 5rem 0 5rem 0;
} }

View File

@@ -10,6 +10,7 @@ h2 {
.teaser-body { .teaser-body {
padding: 20px; padding: 20px;
box-shadow: 0px 5px 10px rgb(0 0 0 / 10%)
} }
.teaser-footer { .teaser-footer {
padding: 20px; padding: 20px;
@@ -66,6 +67,20 @@ h2 {
} }
} }
.anchor_style {
color: inherit;
}
.anchor_style:hover {
text-decoration: none
}
.no-preview:hover {
cursor: pointer;
color: #2490ef;
}
section { section {
padding: 60px 0px; padding: 60px 0px;
} }
@@ -162,7 +177,6 @@ section.lightgray {
// } // }
.instructor-title { .instructor-title {
font-weight: bold;
color: black; color: black;
} }
@@ -331,3 +345,9 @@ section.lightgray {
margin: 40px 0px 0px 20px; margin: 40px 0px 0px 20px;
} }
} }
.no-preview-message {
width: fit-content;
margin: 50px 0px 50px;
color: black;
}

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,3 +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.membership:
utils.redirect_to_lesson(context.course)

View File

@@ -8,31 +8,32 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
{% set invite_link = frappe.utils.get_url() + "/courses/" + course.name + "/" + batch.name + "/join" %}
<div class="container mt-5"> <div class="container mt-5">
{{ widgets.BatchTabs(course=course, batch=batch) }} {{ widgets.BatchTabs(course=course, membership=membership) }}
<div> <div class="course-details mt-5">
<h1 class="mt-5">{{ batch.title }}</h1>
</div>
<div class="course-details">
{{ 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">
<h2>Batch Schedule</h2> <h3>Batch Schedule</h3>
{{ widgets.RenderBatch(course=course, batch=batch) }} {{ widgets.RenderBatch(course=course, batch=batch) }}
</div> </div>
{% if batch.description %} {% if batch.description %}
<h2>Batch Details</h2> <div class="mt-5">
<h3>Batch Details</h3>
{{ frappe.utils.md_to_html(batch.description) }} {{ frappe.utils.md_to_html(batch.description) }}
</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="">
<h2> Invite Members </h2> <h3> Invite Members </h3>
<a href="" class="anchor_style mr-5" id="invite-link" data-link="{{ invite_link }}">Get Batch Invitation <a href="" class="" id="invite-link" data-link="{{ invite_link }}">Get Batch Invitation
Link</a> Link</a>
<small id="copy-message" class="text-muted pull-right" style="display: none;">Copied to Clipboard.</small> <small id="copy-message" class="text-muted" style="display: none;">Copied to Clipboard.</small>
</div> </div>
{% endif %} {% endif %}
@@ -52,7 +53,7 @@
$("#copy-message").slideDown(function () { $("#copy-message").slideDown(function () {
setTimeout(function () { setTimeout(function () {
$("#copy-message").slideUp(); $("#copy-message").slideUp();
}, 5000); }, 2000);
}); });
}) })
}) })

View File

@@ -13,9 +13,9 @@
<div class='page-card-head'> <div class='page-card-head'>
<span class='indicator blue password-box'>Login Required</span> <span class='indicator blue password-box'>Login Required</span>
</div> </div>
<div class=''>Please log in to confirm to join the course {{ batch.course_title }}.</div> <div class=''>Please log in to confirm joining the course {{ batch.course_title }}.</div>
<a type="submit" id="login" class="btn btn-primary w-100" <a type="submit" id="login" class="btn btn-primary w-100"
href="/login?redirect-to=/courses/{{ batch.course }}/{{ batch.name }}/join">{{_("Login")}}</a> href="/login?redirect-to=/courses/{{ batch.course }}/join?batch={{ batch.name }}">{{_("Login")}}</a>
</div> </div>
{% elif already_a_member %} {% elif already_a_member %}
@@ -26,7 +26,7 @@
</div> </div>
<div class=''>You are already a member of the batch {{ batch.title }} for the course {{ batch.course_title }}. <div class=''>You are already a member of the batch {{ batch.title }} for the course {{ batch.course_title }}.
</div> </div>
<a type="submit" id="batch-home" class="btn btn-primary w-100" href="/courses/{{batch.course}}/{{batch.name}}/home">{{_("Go to Batch Home")}}</a> <a type="submit" id="batch-home" class="btn btn-primary w-100" href="">{{_("Go to Batch Home")}}</a>
</div> </div>
{% else %} {% else %}
@@ -38,23 +38,21 @@
<div>Please provide your confirmation to be a part of the batch {{ batch.title }} for the course <div>Please provide your confirmation to be a part of the batch {{ batch.title }} for the course
{{ batch.course_title }}. {{ batch.course_title }}.
</div> </div>
<a type="submit" id="confirm" class="btn btn-primary w-100" data-batch="{{ batch.name | urlencode }}" <a type="submit" id="confirm" class="btn btn-primary w-100">{{_("Confirm")}}</a>
data-course="{{ batch.course | urlencode }}">{{_("Confirm")}}</a>
</div> </div>
{% endif %} {% endif %}
{% endblock %}
{% block script %}
<script> <script>
frappe.ready(() => { frappe.ready(() => {
var confirm_element = $("#confirm"); $("#confirm").click((e) => {
var batch = decodeURIComponent(confirm_element.attr("data-batch"));
var course = decodeURIComponent(confirm_element.attr("data-course"));
confirm_element.click((e) => {
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.name }},
"course": {{ batch.course }}
}, },
"callback": (data) => { "callback": (data) => {
if (data.message == "OK") { if (data.message == "OK") {
@@ -63,7 +61,7 @@
clear: true clear: true
}); });
setTimeout(function () { setTimeout(function () {
window.location.href = "/courses/" + course + "/" + batch + "/home"; window.location.href = "/courses/{{ batch.course }}/home";
}, 2000); }, 2000);
} }
} }

View File

@@ -22,12 +22,20 @@
{% 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 }}" data-batch="{{ batch.name }}">{{ 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 membership or lesson.include_in_preview %}
{{ lesson.render_html() }} {{ lesson.render_html() }}
{% else %}
<div class="no-preview-message">
<span>This lesson is not available for Preview. Please join the course to access this lesson.</span>
<a href="/courses/{{ course.name }}">Checkout Course Details.</a>
</div>
{% endif %}
{{ pagination(prev_chap, prev_url, next_chap, next_url) }} {{ pagination(prev_chap, prev_url, next_chap, next_url) }}
</div> </div>
@@ -53,20 +61,7 @@
{%- block script %} {%- block script %}
{{ super() }} {{ super() }}
<script type="text/javascript">
$(function() {
var batch_name = "{{ batch.name }}";
var lesson_name = "{{ lesson.name }}";
frappe.call("community.lms.api.save_current_lesson", {
"batch_name": batch_name,
"lesson_name": lesson_name
})
})
</script>
{% for ext in page_extensions %} {% for ext in page_extensions %}
{{ ext.render_footer() }} {{ ext.render_footer() }}
{% endfor %} {% endfor %}
{%- endblock %} {%- endblock %}

View File

@@ -1,11 +1,17 @@
frappe.ready(() => { frappe.ready(() => {
if (!$(".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-membership")) {
frappe.call("community.lms.api.save_current_lesson", {
course_name: $(".title").attr("data-course"),
lesson_name: $(".title").attr("data-lesson")
})
}
}) })

View File

@@ -1,6 +1,9 @@
from re import I from re import I
import frappe import frappe
from . import utils from . import utils
from frappe.utils import cstr
from community.www import batch
def get_context(context): def get_context(context):
utils.get_common_context(context) utils.get_common_context(context)
@@ -10,10 +13,12 @@ def get_context(context):
lesson_number = f"{chapter_index}.{lesson_index}" lesson_number = f"{chapter_index}.{lesson_index}"
course_name = context.course.name course_name = context.course.name
if not chapter_index or not lesson_index: if not chapter_index or not lesson_index:
if context.batch:
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"
frappe.local.flags.redirect_location = context.batch.get_learn_url(index_) else:
index_ = "1.1"
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)
@@ -25,16 +30,17 @@ 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.batch.get_learn_url(next_) context.next_url = context.course.get_learn_url(next_) + context.course.query_parameter
context.prev_url = context.batch.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()
def get_chapter_title(course_name, lesson_number): def get_chapter_title(course_name, lesson_number):
if not lesson_number: if not lesson_number:
return return
chapter_index = lesson_number.split(".")[0] lesson_split = cstr(lesson_number).split(".")
lesson_index = lesson_number.split(".")[1] chapter_index = lesson_split[0]
lesson_index = lesson_split[1]
chapter_name = frappe.db.get_value("Chapter", {"course": course_name, "index_": chapter_index}, "name") chapter_name = frappe.db.get_value("Chapter", {"course": course_name, "index_": chapter_index}, "name")
return frappe.db.get_value("Lesson", {"chapter": chapter_name, "index_": lesson_index}, "title") return frappe.db.get_value("Lesson", {"chapter": chapter_name, "index_": lesson_index}, "title")

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 %}
@@ -19,23 +19,19 @@
{% macro MembersList(members) %} {% macro MembersList(members) %}
<div class="mt-5"> <div class="mt-5">
{% for member in members %} {% for member in members %}
<div class="row mb-5"> <div class="d-flex align-items-center">
<div>
{{ widgets.Avatar(member=member, avatar_class="avatar-large") }} {{ widgets.Avatar(member=member, avatar_class="avatar-large") }}
</div> <div class="d-flex flex-column ml-2">
<div class="col"> <div class="d-flex">
<div class="row ml-1"> <a class="anchor_style ml-2" href="/{{member.username}}">
<a class="anchor_style" href="/{{member.username}}">
<h3>{{ member.full_name }}</h3> <h3>{{ member.full_name }}</h3>
</a> </a>
{% if course.is_mentor(member.name) %} {% if course.is_mentor(member.name) %}
<div class="ml-2"> <div class="badge badge-success ml-2 align-self-start">Mentor</div>
<div class="badge badge-success">Mentor</div>
</div>
{% endif %} {% endif %}
</div> </div>
{% if member.bio %} {% if member.bio %}
<i>{{member.bio}}</i> <i class="ml-2">{{member.bio}}</i>
{% endif %} {% endif %}
</div> </div>
</div> </div>

View File

@@ -3,4 +3,5 @@ from . import utils
def get_context(context): def get_context(context):
utils.get_common_context(context) utils.get_common_context(context)
print(context.members[0].bio) if not context.membership:
utils.redirect_to_lesson(context.course)

View File

@@ -24,9 +24,9 @@
{% 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">
<h1>Batch Progress</h1> <h3>Batch Progress</h3>
{% for exercise in report.exercises %} {% for exercise in report.exercises %}
<div class="exercise-submissions"> <div class="exercise-submissions">
<h2>Exercise {{exercise.index_label}}: {{exercise.title}}</h2> <h2>Exercise {{exercise.index_label}}: {{exercise.title}}</h2>

View File

@@ -17,9 +17,8 @@ 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:
self.submissions_by_exercise[s.exercise].append(s) self.submissions_by_exercise[s.exercise].append(s)
@@ -30,11 +29,12 @@ 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 = """
select owner, exercise, name, solution, creation, image select owner, exercise, name, solution, creation, image
from ( from (
@@ -45,7 +45,6 @@ def get_submissions(batch):
""".format(names) """.format(names)
data = frappe.db.sql(sql, values=values, as_dict=True) data = frappe.db.sql(sql, values=values, as_dict=True)
for row in data: for row in data:
row['owner'] = students_map[row['owner']] row['owner'] = students_map[row['owner']]
return data return data

View File

@@ -5,24 +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"] 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
batch = course.get_batch(batch_name)
if not batch or not batch.is_member(frappe.session.user):
frappe.local.flags.redirect_location = "/courses/" + course_name
raise frappe.Redirect
context.course = course context.course = course
membership = course.get_membership(frappe.session.user, batch_name)
if membership:
context.membership = membership
batch = course.get_batch(membership.batch)
if batch:
context.batch = batch context.batch = batch
context.members = batch.get_mentors() + batch.get_students()
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.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"):
frappe.local.flags.redirect_location = course.get_learn_url(index_) + course.query_parameter
raise frappe.Redirect

View File

@@ -12,28 +12,27 @@
<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>
<h1 id="course-title" data-course="{{course.name}}">{{course.title}}</h1> <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>
<div class="row"> <div class="">
<div class="col-lg-8 col-md-12"> <div class="">
<div class="course-details"> <div class="course-details">
{{ CourseVideo(course) }} {{ CourseVideo(course) }}
{{ CourseDescription(course) }} {{ CourseDescription(course) }}
{{ widgets.InstructorSection(instructor=course.get_instructor()) }}
{{ BatchSection(course) }} {{ BatchSection(course) }}
{{ widgets.CourseOutline(course=course, show_link=False) }} {{ widgets.CourseOutline(course=course, show_link=False) }}
</div> </div>
</div> </div>
<div class="col-lg-4 col-md-12">
<div class="sidebar">
{{ widgets.InstructorSection(instructor=course.get_instructor()) }}
</div>
<div class="sidebar">
{{ MentorsSection(course.get_mentors(), course.is_mentor(frappe.session.user), course.name) }}
</div>
</div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
@@ -49,28 +48,31 @@
{% endmacro %} {% endmacro %}
{% macro CourseDescription(course) %} {% macro CourseDescription(course) %}
<h2>Course Description</h2> <div class="mt-5">
<h3>Course Description</h3>
<div class="course-description"> <div class="course-description text-justify">
{{ frappe.utils.md_to_html(course.description) }} {{ frappe.utils.md_to_html(course.description) }}
</div> </div>
</div>
{% endmacro %} {% endmacro %}
{% macro BatchSection(course) %} {% macro BatchSection(course) %}
<div class="row">
<div class="col-lg-8 col-md-12">
{% if course.is_mentor(frappe.session.user) %} {% if course.is_mentor(frappe.session.user) %}
{{ BatchSectionForMentors(course, course.get_batches(mentor=frappe.session.user)) }} {{ BatchSectionForMentors(course, course.get_batches(mentor=frappe.session.user)) }}
{% else %} {% else %}
{{ BatchSectionForStudents(course, course.get_upcoming_batches()) }} {{ BatchSectionForStudents(course, course.get_upcoming_batches()) }}
{% endif %} {% endif %}
</div>
</div>
{% endmacro %} {% endmacro %}
{% macro BatchSectionForMentors(course, mentor_batches) %} {% macro BatchSectionForMentors(course, mentor_batches) %}
<h2>Your Batches</h2> <h2>Your Batches</h2>
{% if mentor_batches %} {% if mentor_batches %}
<!-- <div class="alert alert-secondary">
You are a mentor for this course. Manage your batches or create a new batch from here.
</div> -->
<div class="row"> <div class="row">
{% for batch in mentor_batches %} {% for batch in mentor_batches %}
@@ -80,20 +82,20 @@
{% 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 %}
{% macro BatchSectionForStudents(course, upcoming_batches) %} {% macro BatchSectionForStudents(course, upcoming_batches) %}
{% if upcoming_batches %} {% if upcoming_batches %}
<h2>Upcoming Batches</h2> <div class="mt-5">
<h3 class="upcoming">Upcoming Batches</h3>
<div class="row"> <div class="row">
{% for batch in upcoming_batches %} {% for batch in upcoming_batches %}
<div class="col-lg-4 col-md-6"> <div class="col-lg-4 col-md-6">
@@ -101,5 +103,8 @@
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
{% else %}
<div class="mt-5 upcoming">There are no Upcoming Batches for this course currently.</div>
{% endif %} {% endif %}
</div>
{% endmacro %} {% endmacro %}

View File

@@ -51,20 +51,26 @@ frappe.ready(() => {
}) })
$(".join-batch").click((e) => { $(".join-batch").click((e) => {
e.preventDefault() e.preventDefault();
var course = $(e.currentTarget).attr("data-course")
if (frappe.session.user == "Guest") { if (frappe.session.user == "Guest") {
window.location.href = `/login?redirect-to=/courses/${$(e.currentTarget).attr("data-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") {
frappe.msgprint(__("You are now a student of this course.")) frappe.msgprint(__("You are now a student of this course."));
setTimeout(function () {
window.location.href = `/courses/${course}/home`;
}, 2000);
} }
} }
}) })

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):
batch = course.get_membership(frappe.session.user)
if batch: if batch:
frappe.local.flags.redirect_location = f"/courses/{course.name}/{batch.name}/learn" frappe.local.flags.redirect_location = f"/courses/{course.name}/learn"
raise frappe.Redirect raise frappe.Redirect

View File

@@ -10,19 +10,17 @@
{% block content %} {% block content %}
<section class="top-section" style="padding: 1rem 0rem;"> <section class="top-section" style="padding: 1rem 0rem;">
<div class='container pb-5'>
<h1>{{ 'Courses' }}</h1>
</div>
<div class='container'> <div class='container'>
<h4 class="mt-5">{{ 'All Courses' }}</h4>
<div class="row mt-5"> <div class="row mt-5">
{% for course in courses %} {% for course in courses %}
{{ course_card(course) }} {{ course_card(course) }}
{% endfor %} {% endfor %}
{% if courses %} <!-- {% if courses %}
{% for n in range( (3 - (courses|length)) %3) %} {% for n in range( (3 - (courses|length)) %3) %}
{{ null_card() }} {{ null_card() }}
{% endfor %} {% endfor %}
{% endif %} {% endif %} -->
</div> </div>
</div> </div>
</section> </section>
@@ -31,8 +29,8 @@
{% macro course_card(course) %} {% macro course_card(course) %}
<div class="col-sm-4 mb-4 text-left"> <div class="col-sm-4 mb-4 text-left">
<a class="card-links" style="color: inherit;" href="/courses/{{course.name}}"> <a class="anchor_style" style="color: inherit;" href="/courses/{{course.name}}">
<div class="card h-100"> <div class="card h-100" style="box-shadow: 0px 5px 10px rgb(0 0 0 / 10%);">
<div class='card-body'> <div class='card-body'>
<h5 class='card-title'>{{ course.title }}</h5> <h5 class='card-title'>{{ course.title }}</h5>
{% if course.description %} {% if course.description %}

View File

@@ -1,7 +1,7 @@
{% macro hackathon_card(hackathon) %} {% macro hackathon_card(hackathon) %}
<div class="col-sm-4 mb-4 text-left"> <div class="col-sm-4 mb-4 text-left">
<a href="/hackathons/{{ hackathon.name }}" class="no-decoration no-underline"> <a href="/hackathons/{{ hackathon.name }}" class="no-decoration no-underline">
<div class="card h-100"> <div class="card h-100" style="box-shadow: 0px 5px 10px rgb(0 0 0 / 10%);">
<div class='card-body'> <div class='card-body'>
<h5 class='card-title'>{{ hackathon.name }}</h5> <h5 class='card-title'>{{ hackathon.name }}</h5>
</div> </div>
@@ -12,7 +12,7 @@
{% macro null_card() %} {% macro null_card() %}
<div class="col-sm-4 mb-4 text-left"> <div class="col-sm-4 mb-4 text-left">
<div class="h-100 d-none d-sm-block" style="border: 1px solid rgba(209,216,221,0.5);border-radius: 0.25rem;background-color: rgb(250, 251, 252);"> <div class="h-100 d-none d-sm-block" style="box-shadow: 0px 5px 10px rgb(0 0 0 / 10%);border-radius: 0.25rem;background-color: rgb(250, 251, 252);">
</div> </div>
</div> </div>
{% endmacro %} {% endmacro %}