diff --git a/community/hackathon/doctype/community_project_member/community_project_member.py b/community/hackathon/doctype/community_project_member/community_project_member.py index 7a7b7559..6068c39c 100644 --- a/community/hackathon/doctype/community_project_member/community_project_member.py +++ b/community/hackathon/doctype/community_project_member/community_project_member.py @@ -5,11 +5,12 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document +from frappe import _ class CommunityProjectMember(Document): def validate(self): self.validate_if_already_member() - + def validate_if_already_member(self): if frappe.get_all("Community Project Member", {"owner": self.owner}): frappe.throw(_("You have already applied for the membership of this project.")) diff --git a/community/hooks.py b/community/hooks.py index ea739a8f..809562ac 100644 --- a/community/hooks.py +++ b/community/hooks.py @@ -136,15 +136,15 @@ primary_rules = [ {"from_route": "/hackathons//", "to_route": "hackathons/project"}, {"from_route": "/dashboard", "to_route": ""}, {"from_route": "/add-a-new-batch", "to_route": "add-a-new-batch"}, - {"from_route": "/courses///home", "to_route": "batch/home"}, - {"from_route": "/courses///learn", "to_route": "batch/learn"}, - {"from_route": "/courses///learn/.", "to_route": "batch/learn"}, - {"from_route": "/courses///schedule", "to_route": "batch/schedule"}, - {"from_route": "/courses///members", "to_route": "batch/members"}, - {"from_route": "/courses///discuss", "to_route": "batch/discuss"}, - {"from_route": "/courses///about", "to_route": "batch/about"}, - {"from_route": "/courses///progress", "to_route": "batch/progress"}, - {"from_route": "/courses///join", "to_route": "batch/join"} + {"from_route": "/courses//home", "to_route": "batch/home"}, + {"from_route": "/courses//learn", "to_route": "batch/learn"}, + {"from_route": "/courses//learn/.", "to_route": "batch/learn"}, + {"from_route": "/courses//schedule", "to_route": "batch/schedule"}, + {"from_route": "/courses//members", "to_route": "batch/members"}, + {"from_route": "/courses//discuss", "to_route": "batch/discuss"}, + {"from_route": "/courses//about", "to_route": "batch/about"}, + {"from_route": "/courses//progress", "to_route": "batch/progress"}, + {"from_route": "/courses//join", "to_route": "batch/join"} ] # Any frappe default URL is blocked by profile-rules, add it here to unblock it diff --git a/community/lms/api.py b/community/lms/api.py index 1ed198e1..089ad86d 100644 --- a/community/lms/api.py +++ b/community/lms/api.py @@ -15,13 +15,6 @@ def autosave_section(section, code): doc.insert() 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() def submit_solution(exercise, code): """Submits a solution. diff --git a/community/lms/doctype/code_revision/__init__.py b/community/lms/doctype/code_revision/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/community/lms/doctype/code_revision/code_revision.js b/community/lms/doctype/code_revision/code_revision.js deleted file mode 100644 index 4f9451ce..00000000 --- a/community/lms/doctype/code_revision/code_revision.js +++ /dev/null @@ -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) { - - // } -}); diff --git a/community/lms/doctype/code_revision/code_revision.json b/community/lms/doctype/code_revision/code_revision.json deleted file mode 100644 index 4b2cc036..00000000 --- a/community/lms/doctype/code_revision/code_revision.json +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/community/lms/doctype/code_revision/code_revision.py b/community/lms/doctype/code_revision/code_revision.py deleted file mode 100644 index 5ca32baa..00000000 --- a/community/lms/doctype/code_revision/code_revision.py +++ /dev/null @@ -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 diff --git a/community/lms/doctype/code_revision/test_code_revision.py b/community/lms/doctype/code_revision/test_code_revision.py deleted file mode 100644 index 3ce8a443..00000000 --- a/community/lms/doctype/code_revision/test_code_revision.py +++ /dev/null @@ -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 diff --git a/community/lms/doctype/invite_request/invite_request.py b/community/lms/doctype/invite_request/invite_request.py index 00bc18c2..325f2611 100644 --- a/community/lms/doctype/invite_request/invite_request.py +++ b/community/lms/doctype/invite_request/invite_request.py @@ -58,7 +58,8 @@ def create_invite_request(invite_email): frappe.get_doc({ "doctype": "Invite Request", - "invite_email": invite_email + "invite_email": invite_email, + "status": "Approved" }).save(ignore_permissions=True) return "OK" diff --git a/community/lms/doctype/invite_request/test_invite_request.py b/community/lms/doctype/invite_request/test_invite_request.py index 31bc7afe..20fe531d 100644 --- a/community/lms/doctype/invite_request/test_invite_request.py +++ b/community/lms/doctype/invite_request/test_invite_request.py @@ -18,7 +18,7 @@ class TestInviteRequest(unittest.TestCase): filters={"invite_email": "test_invite@example.com"}, fieldname=["invite_email", "status", "signup_email"], as_dict=True) - self.assertEqual(invite.status, "Pending") + self.assertEqual(invite.status, "Approved") self.assertEqual(invite.signup_email, None) def test_create_invite_request_update(self): diff --git a/community/lms/doctype/lesson/lesson.json b/community/lms/doctype/lesson/lesson.json index 4772b255..18c20147 100644 --- a/community/lms/doctype/lesson/lesson.json +++ b/community/lms/doctype/lesson/lesson.json @@ -8,11 +8,13 @@ "field_order": [ "chapter", "lesson_type", + "include_in_preview", + "column_break_4", "title", "index_", "index_label", - "body", - "sections" + "section_break_6", + "body" ], "fields": [ { @@ -47,23 +49,31 @@ "fieldtype": "Markdown Editor", "label": "Body" }, - { - "fieldname": "sections", - "fieldtype": "Table", - "label": "Sections", - "options": "LMS Section" - }, { "fieldname": "index_label", "fieldtype": "Data", "in_list_view": 1, "label": "Index Label", "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, "links": [], - "modified": "2021-06-01 05:30:48.127494", + "modified": "2021-06-11 19:03:23.138165", "modified_by": "Administrator", "module": "LMS", "name": "Lesson", diff --git a/community/lms/doctype/lesson/lesson.py b/community/lms/doctype/lesson/lesson.py index 52d8bf28..c96d3315 100644 --- a/community/lms/doctype/lesson/lesson.py +++ b/community/lms/doctype/lesson/lesson.py @@ -5,7 +5,6 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document -from ...section_parser import SectionParser from ...md import markdown_to_html, find_macros class Lesson(Document): @@ -39,9 +38,6 @@ class Lesson(Document): def render_html(self): return markdown_to_html(self.body) - def get_sections(self): - return sorted(self.get('sections'), key=lambda s: s.index) - def get_exercises(self): if not self.body: return [] @@ -50,30 +46,6 @@ class Lesson(Document): exercises = [value for name, value in macros if name == "Exercise"] 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): return frappe.db.get_value("LMS Course Progress", {"lesson": self.name, "owner": frappe.session.user}, "status") @@ -98,11 +70,7 @@ def save_progress(lesson, batch): return lesson_details = frappe.get_doc("Lesson", lesson) - dynamic_content = frappe.db.count("LMS Section", - filters={ - "type": ["not in", ["example", "text"]], - "parent": lesson_details.name - }) + dynamic_content = find_macros(lesson_details.body) status = "Complete" if dynamic_content: @@ -121,12 +89,11 @@ def update_progress(lesson): 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.status = "Complete" - course_progress.save() + course_progress.save(ignore_permissions=True) 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 - print(exercise_names) query = { "exercise": ["in", exercise_names], "owner": user diff --git a/community/lms/doctype/lms_batch/lms_batch.py b/community/lms/doctype/lms_batch/lms_batch.py index 185134ae..391eac7e 100644 --- a/community/lms/doctype/lms_batch/lms_batch.py +++ b/community/lms/doctype/lms_batch/lms_batch.py @@ -80,11 +80,6 @@ class LMSBatch(Document): membership = self.get_membership(user) 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() def save_message(message, batch): doc = frappe.get_doc({ diff --git a/community/lms/doctype/lms_batch_membership/lms_batch_membership.json b/community/lms/doctype/lms_batch_membership/lms_batch_membership.json index 68d2eaed..6a0cc143 100644 --- a/community/lms/doctype/lms_batch_membership/lms_batch_membership.json +++ b/community/lms/doctype/lms_batch_membership/lms_batch_membership.json @@ -13,7 +13,8 @@ "course", "member_type", "role", - "current_lesson" + "current_lesson", + "is_current" ], "fields": [ { @@ -80,11 +81,19 @@ "fieldtype": "Data", "label": "Memeber Username", "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, "links": [], - "modified": "2021-05-24 12:40:57.125694", + "modified": "2021-06-14 10:24:35.425498", "modified_by": "Administrator", "module": "LMS", "name": "LMS Batch Membership", diff --git a/community/lms/doctype/lms_batch_membership/lms_batch_membership.py b/community/lms/doctype/lms_batch_membership/lms_batch_membership.py index 7d714d46..f7a39b74 100644 --- a/community/lms/doctype/lms_batch_membership/lms_batch_membership.py +++ b/community/lms/doctype/lms_batch_membership/lms_batch_membership.py @@ -53,3 +53,13 @@ def create_membership(batch, member=None, member_type="Student", role="Member"): "member": member or frappe.session.user }).save(ignore_permissions=True) return "OK" + +@frappe.whitelist() +def update_current_membership(batch, course, member=frappe.session.user): + 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) diff --git a/community/lms/doctype/lms_course/lms_course.py b/community/lms/doctype/lms_course/lms_course.py index b3d08ef9..e4a35ad2 100644 --- a/community/lms/doctype/lms_course/lms_course.py +++ b/community/lms/doctype/lms_course/lms_course.py @@ -8,6 +8,7 @@ from frappe.model.document import Document import json from ...utils import slugify from community.query import find, find_all +from frappe.utils import flt class LMSCourse(Document): @staticmethod @@ -112,7 +113,7 @@ class LMSCourse(Document): """Returns all chapters of this course. """ # 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): return find("LMS Batch", name=batch_name, course=self.name) @@ -186,6 +187,26 @@ class LMSCourse(Document): exercise.save() i += 1 + def get_learn_url(self, lesson_number): + if not lesson_number: + return + return f"/courses/{self.name}/learn/{lesson_number}" + + def get_current_batch(self, member=frappe.session.user): + current_membership = frappe.get_all("LMS Batch Membership", {"member": member, "course": self.name, "is_current": 1}, pluck="batch") + print(current_membership, member, self.name) + if len(current_membership): + return current_membership[0] + print(frappe.db.get_value("LMS Batch Membership", {"member": member, "course": self.name}, "batch")) + return frappe.db.get_value("LMS Batch Membership", {"member": member, "course": self.name}, "batch") + + 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", "is_current"]) + print(all_memberships) + for membership in all_memberships: + membership.batch_title = frappe.db.get_value("LMS Batch", membership.batch, "title") + return all_memberships def get_outline(self): return CourseOutline(self) @@ -197,6 +218,7 @@ class CourseOutline: self.lessons = self.get_lessons() def get_next(self, current): + current = flt(current) numbers = sorted(lesson['number'] for lesson in self.lessons) try: index = numbers.index(current) @@ -205,6 +227,7 @@ class CourseOutline: return None def get_prev(self, current): + current = flt(current) numbers = sorted(lesson['number'] for lesson in self.lessons) try: index = numbers.index(current) @@ -228,7 +251,7 @@ class CourseOutline: chapter_numbers = {c['name']: c['index_'] for c in self.chapters} 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 @frappe.whitelist() diff --git a/community/lms/doctype/lms_section/__init__.py b/community/lms/doctype/lms_section/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/community/lms/doctype/lms_section/lms_section.json b/community/lms/doctype/lms_section/lms_section.json deleted file mode 100644 index 3b056485..00000000 --- a/community/lms/doctype/lms_section/lms_section.json +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/community/lms/doctype/lms_section/lms_section.py b/community/lms/doctype/lms_section/lms_section.py deleted file mode 100644 index 65c33f46..00000000 --- a/community/lms/doctype/lms_section/lms_section.py +++ /dev/null @@ -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"" - - 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 diff --git a/community/lms/models.py b/community/lms/models.py index 00c910c3..2e7249d7 100644 --- a/community/lms/models.py +++ b/community/lms/models.py @@ -2,4 +2,4 @@ """ from .doctype.lms_course.lms_course import LMSCourse as Course from .doctype.lms_sketch.lms_sketch import LMSSketch as Sketch - +from .doctype.lms_batch_membership.lms_batch_membership import LMSBatchMembership as Membership diff --git a/community/lms/section_parser.py b/community/lms/section_parser.py deleted file mode 100644 index 183a7c6b..00000000 --- a/community/lms/section_parser.py +++ /dev/null @@ -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'' diff --git a/community/lms/web_form/add_a_new_batch/add_a_new_batch.json b/community/lms/web_form/add_a_new_batch/add_a_new_batch.json index 76735390..caf901ff 100644 --- a/community/lms/web_form/add_a_new_batch/add_a_new_batch.json +++ b/community/lms/web_form/add_a_new_batch/add_a_new_batch.json @@ -11,7 +11,7 @@ "apply_document_permissions": 0, "button_label": "Save", "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", "docstatus": 0, "doctype": "Web Form", @@ -19,7 +19,7 @@ "is_standard": 1, "login_required": 1, "max_attachment_size": 0, - "modified": "2021-06-02 15:52:06.383260", + "modified": "2021-06-14 15:28:08.206622", "modified_by": "Administrator", "module": "LMS", "name": "add-a-new-batch", diff --git a/community/lms/web_form/join_a_batch/__init__.py b/community/lms/web_form/join_a_batch/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/community/lms/web_form/join_a_batch/join_a_batch.js b/community/lms/web_form/join_a_batch/join_a_batch.js deleted file mode 100644 index 699703c5..00000000 --- a/community/lms/web_form/join_a_batch/join_a_batch.js +++ /dev/null @@ -1,3 +0,0 @@ -frappe.ready(function() { - // bind events here -}) \ No newline at end of file diff --git a/community/lms/web_form/join_a_batch/join_a_batch.json b/community/lms/web_form/join_a_batch/join_a_batch.json deleted file mode 100644 index 9afd7801..00000000 --- a/community/lms/web_form/join_a_batch/join_a_batch.json +++ /dev/null @@ -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 - } - ] -} \ No newline at end of file diff --git a/community/lms/web_form/join_a_batch/join_a_batch.py b/community/lms/web_form/join_a_batch/join_a_batch.py deleted file mode 100644 index 2334f8b2..00000000 --- a/community/lms/web_form/join_a_batch/join_a_batch.py +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import unicode_literals - -import frappe - -def get_context(context): - # do your magic here - pass diff --git a/community/lms/widgets/BatchTabs.html b/community/lms/widgets/BatchTabs.html index 48378660..5a3b8a0f 100644 --- a/community/lms/widgets/BatchTabs.html +++ b/community/lms/widgets/BatchTabs.html @@ -1,28 +1,49 @@
- Courses /{% if course.is_mentor(frappe.session.user) %} {{ course.title }} {% else %} {{ course.title }} {% endif %} + Courses /{% if course.is_mentor(frappe.session.user) %} {{ course.title }} {% else %} + {{ course.title }} {% endif %} + {% set all_memberships = course.get_all_memberships() %} + {% if all_memberships | length > 1 %} + + + {% endif %}
+{% if not batch %} +{% set display_class = "hide" %} +{% else %} +{% set display_class = "" %} +{% endif %} @@ -35,5 +56,21 @@ else { $("#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(); + } + }) + }) }) diff --git a/community/lms/widgets/ChapterTeaser.html b/community/lms/widgets/ChapterTeaser.html index 1678b1bb..b0cf37c9 100644 --- a/community/lms/widgets/ChapterTeaser.html +++ b/community/lms/widgets/ChapterTeaser.html @@ -1,18 +1,54 @@
-

{{index}}. {{ chapter.title }}

+
{{index}}. {{ chapter.title }}
{{ chapter.description or "" }}
{% for lesson in chapter.get_lessons() %}
- {{ lesson.title }} + {{ lesson.title }} {% if show_progress and not course.is_mentor(frappe.session.user) and lesson.get_progress() %} - {{ lesson.get_progress() }} + {{ lesson.get_progress() }} {% endif %}
{% endfor %}
+ + diff --git a/community/lms/widgets/CourseOutline.html b/community/lms/widgets/CourseOutline.html index 249013ff..b4b5629c 100644 --- a/community/lms/widgets/CourseOutline.html +++ b/community/lms/widgets/CourseOutline.html @@ -1,5 +1,7 @@ -

Course Outline

+
+

Course Outline

-{% 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)}} -{% endfor %} + {% 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)}} + {% endfor %} +
diff --git a/community/lms/widgets/InstructorSection.html b/community/lms/widgets/InstructorSection.html index d7634b65..367148dd 100644 --- a/community/lms/widgets/InstructorSection.html +++ b/community/lms/widgets/InstructorSection.html @@ -1,5 +1,6 @@ -

Instructor

-
{{instructor.full_name}}
-
Created {{instructor.get_course_count()}} courses
+ {{ widgets.Avatar(member=instructor, avatar_class="avatar-medium") }} + {{ instructor.full_name }} +
Course Creator
+
diff --git a/community/lms/widgets/RenderBatch.html b/community/lms/widgets/RenderBatch.html index 60e77784..cf23b7a4 100644 --- a/community/lms/widgets/RenderBatch.html +++ b/community/lms/widgets/RenderBatch.html @@ -1,6 +1,6 @@
-
Session every {{batch.sessions_on}}
+
Session every {{batch.sessions_on}}
{{frappe.utils.format_time(batch.start_time, "short")}} - {{frappe.utils.format_time(batch.end_time, "short")}}
@@ -18,7 +18,8 @@
{% if can_manage %} - Manage + Manage {% elif can_join %} diff --git a/community/public/css/style.css b/community/public/css/style.css index 699d8dfc..448b6431 100644 --- a/community/public/css/style.css +++ b/community/public/css/style.css @@ -82,6 +82,7 @@ body { border-radius: 10px; margin: 10px 0px; background: white; + box-shadow: 0px 5px 10px rgb(0 0 0 / 10%); border: 1px solid #ddc; } @@ -156,19 +157,6 @@ img.profile-photo { line-height: 51px; } -.anchor_style { - color: inherit; -} - -a:hover { - text-decoration: none; - color: inherit; -} - -.anchor_style:hover { - text-decoration: underline -} - section { padding: 5rem 0 5rem 0; } diff --git a/community/public/css/style.less b/community/public/css/style.less index c265dafe..b74cd666 100644 --- a/community/public/css/style.less +++ b/community/public/css/style.less @@ -10,6 +10,7 @@ h2 { .teaser-body { padding: 20px; + box-shadow: 0px 5px 10px rgb(0 0 0 / 10%) } .teaser-footer { 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 { padding: 60px 0px; } @@ -162,7 +177,6 @@ section.lightgray { // } .instructor-title { - font-weight: bold; color: black; } @@ -331,3 +345,9 @@ section.lightgray { margin: 40px 0px 0px 20px; } } + +.no-preview-message { + width: fit-content; + margin: 50px 0px 50px; + color: black; +} diff --git a/community/www/batch/discuss.py b/community/www/batch/discuss.py index 5da3db2d..95ffcb34 100644 --- a/community/www/batch/discuss.py +++ b/community/www/batch/discuss.py @@ -4,3 +4,5 @@ from . import utils def get_context(context): utils.get_common_context(context) context.messages = context.batch.get_messages() + if not context.batch: + utils.redirect_to_lesson(context.course) diff --git a/community/www/batch/home.html b/community/www/batch/home.html index db439963..4b930430 100644 --- a/community/www/batch/home.html +++ b/community/www/batch/home.html @@ -8,31 +8,33 @@ {% endblock %} {% block content %} -{% set invite_link = frappe.utils.get_url() + "/courses/" + course.name + "/" + batch.name + "/join" %} +{% set invite_link = frappe.utils.get_url() + "/courses/" + course.name + "/join?batch=" + batch.name %}
{{ widgets.BatchTabs(course=course, batch=batch) }} -
+ +
{{ widgets.CourseOutline(course=course, batch=batch, show_link=True, show_progress=True) }}
-

Batch Schedule

+

Batch Schedule

{{ widgets.RenderBatch(course=course, batch=batch) }}
{% if batch.description %} -

Batch Details

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

Batch Details

+ {{ frappe.utils.md_to_html(batch.description) }} +
{% endif %} {% if course.is_mentor(frappe.session.user) %}
-

Invite Members

- Get Batch Invitation +

Invite Members

+
Get Batch Invitation Link - +
{% endif %} @@ -52,7 +54,7 @@ $("#copy-message").slideDown(function () { setTimeout(function () { $("#copy-message").slideUp(); - }, 5000); + }, 2000); }); }) }) diff --git a/community/www/batch/home.py b/community/www/batch/home.py index 48be81c9..bb2a53a2 100644 --- a/community/www/batch/home.py +++ b/community/www/batch/home.py @@ -3,3 +3,5 @@ from . import utils def get_context(context): utils.get_common_context(context) + if not context.batch: + utils.redirect_to_lesson(context.course) diff --git a/community/www/batch/join.html b/community/www/batch/join.html index 0bcb73e9..0b2ba3ac 100644 --- a/community/www/batch/join.html +++ b/community/www/batch/join.html @@ -13,9 +13,9 @@
Login Required
-
Please log in to confirm to join the course {{ batch.course_title }}.
+
Please log in to confirm joining the course {{ batch.course_title }}.
{{_("Login")}} + href="/login?redirect-to=/courses/{{ batch.course }}/join?batch={{ batch.name }}">{{_("Login")}}
{% elif already_a_member %} @@ -26,7 +26,7 @@
You are already a member of the batch {{ batch.title }} for the course {{ batch.course_title }}.
- {{_("Go to Batch Home")}} + {{_("Go to Batch Home")}}
{% else %} @@ -38,23 +38,20 @@
Please provide your confirmation to be a part of the batch {{ batch.title }} for the course {{ batch.course_title }}.
- {{_("Confirm")}} + {{_("Confirm")}}
{% endif %} +{% endblock %} +{% block script %} {% endblock %} diff --git a/community/www/batch/learn.html b/community/www/batch/learn.html index 17a12ff2..da9834b4 100644 --- a/community/www/batch/learn.html +++ b/community/www/batch/learn.html @@ -25,9 +25,16 @@ {{ widgets.BatchTabs(course=course, batch=batch) }}
-

{{ lesson.title }}

+

{{ lesson.title }}

+ {% if batch or lesson.include_in_preview %} {{ lesson.render_html() }} + {% else %} +
+ This lesson is not available for Preview. Please join a batch to access the complete course. + Checkout Upcoming Batches +
+ {% endif %} {{ pagination(prev_chap, prev_url, next_chap, next_url) }}
@@ -53,18 +60,6 @@ {%- block script %} {{ super() }} - - {% for ext in page_extensions %} {{ ext.render_footer() }} {% endfor %} diff --git a/community/www/batch/learn.js b/community/www/batch/learn.js index 4410fb08..05fbcb33 100644 --- a/community/www/batch/learn.js +++ b/community/www/batch/learn.js @@ -1,5 +1,5 @@ frappe.ready(() => { - if (!$(".title").hasClass("is_mentor")) { + if ($(".title").attr("data-batch") && !$(".title").hasClass("is_mentor")) { frappe.call({ method: "community.lms.doctype.lesson.lesson.save_progress", args: { @@ -8,4 +8,10 @@ frappe.ready(() => { } }) } + if ($(".title").attr("data-batch")) { + frappe.call("community.lms.api.save_current_lesson", { + "batch_name": $(".title").attr("data-batch"), + "lesson_name": $(".title").attr("data-name") + }) + } }) diff --git a/community/www/batch/learn.py b/community/www/batch/learn.py index d0d4f38e..241bc1e7 100644 --- a/community/www/batch/learn.py +++ b/community/www/batch/learn.py @@ -1,6 +1,9 @@ from re import I import frappe from . import utils +from frappe.utils import cstr + +from community.www import batch def get_context(context): utils.get_common_context(context) @@ -10,10 +13,12 @@ def get_context(context): lesson_number = f"{chapter_index}.{lesson_index}" course_name = context.course.name - if not chapter_index or not lesson_index: - 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_) + if context.batch: + index_ = get_lesson_index(context.course, context.batch, frappe.session.user) or "1.1" + else: + index_ = "1.1" + frappe.local.flags.redirect_location = context.course.get_learn_url(index_) raise frappe.Redirect context.lesson = context.course.get_lesson(chapter_index, lesson_index) @@ -25,16 +30,17 @@ def get_context(context): next_ = outline.get_next(lesson_number) context.prev_chap = get_chapter_title(course_name, prev_) context.next_chap = get_chapter_title(course_name, next_) - context.next_url = context.batch.get_learn_url(next_) - context.prev_url = context.batch.get_learn_url(prev_) + context.next_url = context.course.get_learn_url(next_) + context.prev_url = context.course.get_learn_url(prev_) context.page_extensions = get_page_extensions() def get_chapter_title(course_name, lesson_number): if not lesson_number: return - chapter_index = lesson_number.split(".")[0] - lesson_index = lesson_number.split(".")[1] + lesson_split = cstr(lesson_number).split(".") + chapter_index = lesson_split[0] + lesson_index = lesson_split[1] 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") diff --git a/community/www/batch/members.html b/community/www/batch/members.html index 035e12dd..32baa20d 100644 --- a/community/www/batch/members.html +++ b/community/www/batch/members.html @@ -19,23 +19,19 @@ {% macro MembersList(members) %}
{% for member in members %} -
-
- {{ widgets.Avatar(member=member, avatar_class="avatar-large") }} -
-
-
- +
+ {{ widgets.Avatar(member=member, avatar_class="avatar-large") }} +
+
+

{{ member.full_name }}

{% if course.is_mentor(member.name) %} -
-
Mentor
-
+
Mentor
{% endif %}
{% if member.bio %} - {{member.bio}} + {{member.bio}} {% endif %}
diff --git a/community/www/batch/members.py b/community/www/batch/members.py index bdf6e2c3..bb2a53a2 100644 --- a/community/www/batch/members.py +++ b/community/www/batch/members.py @@ -3,4 +3,5 @@ from . import utils def get_context(context): utils.get_common_context(context) - print(context.members[0].bio) + if not context.batch: + utils.redirect_to_lesson(context.course) diff --git a/community/www/batch/progress.html b/community/www/batch/progress.html index 983077b1..0762d835 100644 --- a/community/www/batch/progress.html +++ b/community/www/batch/progress.html @@ -26,7 +26,7 @@
{{ widgets.BatchTabs(course=course, batch=batch) }}
-

Batch Progress

+

Batch Progress

{% for exercise in report.exercises %}

Exercise {{exercise.index_label}}: {{exercise.title}}

diff --git a/community/www/batch/progress.py b/community/www/batch/progress.py index adfb72dd..70024864 100644 --- a/community/www/batch/progress.py +++ b/community/www/batch/progress.py @@ -19,7 +19,6 @@ class BatchReport: def __init__(self, course, batch): self.submissions = get_submissions(batch) self.exercises = self.get_exercises(course.name) - self.submissions_by_exercise = defaultdict(list) for s in self.submissions: self.submissions_by_exercise[s.exercise].append(s) @@ -34,7 +33,6 @@ def get_submissions(batch): students = batch.get_students() students_map = {s.email: s for s in students} names, values = nparams("s", students_map.keys()) - sql = """ select owner, exercise, name, solution, creation, image from ( @@ -45,7 +43,6 @@ def get_submissions(batch): """.format(names) data = frappe.db.sql(sql, values=values, as_dict=True) - for row in data: row['owner'] = students_map[row['owner']] return data diff --git a/community/www/batch/utils.py b/community/www/batch/utils.py index 24b5dd32..b99fb129 100644 --- a/community/www/batch/utils.py +++ b/community/www/batch/utils.py @@ -5,24 +5,26 @@ def get_common_context(context): context.no_cache = 1 course_name = frappe.form_dict["course"] - batch_name = frappe.form_dict["batch"] course = Course.find(course_name) if not course: context.template = "www/404.html" return + batch_name = course.get_current_batch() 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.batch = batch + if batch_name: + context.members = batch.get_mentors() + batch.get_students() + context.member_count = len(context.members) + context.course = course - context.batch = batch - context.members = batch.get_mentors() + batch.get_students() - context.member_count = len(context.members) context.livecode_url = get_livecode_url() def get_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_) + raise frappe.Redirect diff --git a/community/www/courses/course.html b/community/www/courses/course.html index 0d4d7ccc..1401ef1f 100644 --- a/community/www/courses/course.html +++ b/community/www/courses/course.html @@ -12,28 +12,20 @@
Courses / {{ course.title }}
-

{{course.title}}

+

{{course.title}}

{{ course.short_introduction }}
-
-
+
+
{{ CourseVideo(course) }} - {{ CourseDescription(course) }} + {{ widgets.InstructorSection(instructor=course.get_instructor()) }} {{ BatchSection(course) }} {{ widgets.CourseOutline(course=course, show_link=False) }}
-
- - -
{% endblock %} @@ -49,28 +41,31 @@ {% endmacro %} {% macro CourseDescription(course) %} -

Course Description

+
+

Course Description

-
- {{ frappe.utils.md_to_html(course.description) }} +
+ {{ frappe.utils.md_to_html(course.description) }} +
{% endmacro %} {% macro BatchSection(course) %} -{% if course.is_mentor(frappe.session.user) %} -{{ BatchSectionForMentors(course, course.get_batches(mentor=frappe.session.user)) }} -{% else %} -{{ BatchSectionForStudents(course, course.get_upcoming_batches()) }} -{% endif %} +
+
+ {% if course.is_mentor(frappe.session.user) %} + {{ BatchSectionForMentors(course, course.get_batches(mentor=frappe.session.user)) }} + {% else %} + {{ BatchSectionForStudents(course, course.get_upcoming_batches()) }} + {% endif %} +
+
{% endmacro %} {% macro BatchSectionForMentors(course, mentor_batches) %}

Your Batches

{% if mentor_batches %} -
{% for batch in mentor_batches %} @@ -92,14 +87,17 @@ {% macro BatchSectionForStudents(course, upcoming_batches) %} {% if upcoming_batches %} -

Upcoming Batches

- -
- {% for batch in upcoming_batches %} -
- {{ widgets.RenderBatch(course=course, batch=batch, can_join=True) }} +
+

Upcoming Batches

+
+ {% for batch in upcoming_batches %} +
+ {{ widgets.RenderBatch(course=course, batch=batch, can_join=True) }} +
+ {% endfor %}
- {% endfor %} -
+{% else %} +
There are no Upcoming Batches for this course currently.
{% endif %} +
{% endmacro %} diff --git a/community/www/courses/course.js b/community/www/courses/course.js index ae77bdb0..58928149 100644 --- a/community/www/courses/course.js +++ b/community/www/courses/course.js @@ -1,72 +1,92 @@ frappe.ready(() => { - if (frappe.session.user != "Guest") { - frappe.call({ - 'method': 'community.lms.doctype.lms_mentor_request.lms_mentor_request.has_requested', - 'args': { - course: decodeURIComponent($("#course-title").attr("data-course")), - }, - 'callback': (data) => { - if (data.message > 0) { - $("#mentor-request").addClass("hide"); - $("#already-applied").removeClass("hide") - } - } - }) - } + if (frappe.session.user != "Guest") { + frappe.call({ + 'method': 'community.lms.doctype.lms_mentor_request.lms_mentor_request.has_requested', + 'args': { + course: decodeURIComponent($("#course-title").attr("data-course")), + }, + 'callback': (data) => { + if (data.message > 0) { + $("#mentor-request").addClass("hide"); + $("#already-applied").removeClass("hide") + } + } + }) + } - $("#apply-now").click((e) => { + $("#apply-now").click((e) => { e.preventDefault(); - if (frappe.session.user == "Guest") { - window.location.href = `/login?redirect-to=/courses/${$(e.currentTarget).attr("data-course")}`; - return; - } - frappe.call({ - "method": "community.lms.doctype.lms_mentor_request.lms_mentor_request.create_request", - "args": { - "course": decodeURIComponent($(e.currentTarget).attr("data-course")) - }, - "callback": (data) => { - if (data.message == "OK") { - $("#mentor-request").addClass("hide"); - $("#already-applied").removeClass("hide") - } - } - }) - }) + if (frappe.session.user == "Guest") { + window.location.href = `/login?redirect-to=/courses/${$(e.currentTarget).attr("data-course")}`; + return; + } + frappe.call({ + "method": "community.lms.doctype.lms_mentor_request.lms_mentor_request.create_request", + "args": { + "course": decodeURIComponent($(e.currentTarget).attr("data-course")) + }, + "callback": (data) => { + if (data.message == "OK") { + $("#mentor-request").addClass("hide"); + $("#already-applied").removeClass("hide") + } + } + }) + }) - $("#cancel-request").click((e) => { + $("#cancel-request").click((e) => { e.preventDefault() - frappe.call({ - "method": "community.lms.doctype.lms_mentor_request.lms_mentor_request.cancel_request", - "args": { - "course": decodeURIComponent($(e.currentTarget).attr("data-course")) - }, - "callback": (data) => { - if (data.message == "OK") { - $("#mentor-request").removeClass("hide"); - $("#already-applied").addClass("hide") - } - } - }) - }) + frappe.call({ + "method": "community.lms.doctype.lms_mentor_request.lms_mentor_request.cancel_request", + "args": { + "course": decodeURIComponent($(e.currentTarget).attr("data-course")) + }, + "callback": (data) => { + if (data.message == "OK") { + $("#mentor-request").removeClass("hide"); + $("#already-applied").addClass("hide") + } + } + }) + }) - $(".join-batch").click((e) => { - e.preventDefault() - if (frappe.session.user == "Guest") { - window.location.href = `/login?redirect-to=/courses/${$(e.currentTarget).attr("data-course")}`; - return; - } - batch = decodeURIComponent($(e.currentTarget).attr("data-batch")) - frappe.call({ - "method": "community.lms.doctype.lms_batch_membership.lms_batch_membership.create_membership", - "args": { - "batch": batch - }, - "callback": (data) => { - if (data.message == "OK") { - frappe.msgprint(__("You are now a student of this course.")) - } - } - }) - }) + $(".join-batch").click((e) => { + e.preventDefault(); + var course = $(e.currentTarget).attr("data-course") + if (frappe.session.user == "Guest") { + window.location.href = `/login?redirect-to=/courses/${course}`; + return; + } + batch = decodeURIComponent($(e.currentTarget).attr("data-batch")) + frappe.call({ + "method": "community.lms.doctype.lms_batch_membership.lms_batch_membership.create_membership", + "args": { + "batch": batch + }, + "callback": (data) => { + if (data.message == "OK") { + frappe.msgprint(__("You are now a student of this course.")); + setTimeout(function () { + window.location.href = `/courses/${course}/home`; + }, 2000); + } + } + }) + }) + + $(".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`; + } + }) + }) }) diff --git a/community/www/courses/course.py b/community/www/courses/course.py index 69957fb3..e2ff459f 100644 --- a/community/www/courses/course.py +++ b/community/www/courses/course.py @@ -19,6 +19,6 @@ def get_context(context): batch = course.get_student_batch(frappe.session.user) 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 diff --git a/community/www/courses/index.html b/community/www/courses/index.html index 6a69235c..89de4c19 100644 --- a/community/www/courses/index.html +++ b/community/www/courses/index.html @@ -10,19 +10,17 @@ {% block content %}
-
-

{{ 'Courses' }}

-
+

{{ 'All Courses' }}

{% for course in courses %} {{ course_card(course) }} {% endfor %} - {% if courses %} +
@@ -31,8 +29,8 @@ {% macro course_card(course) %}