diff --git a/community/hooks.py b/community/hooks.py index 0c21b608..3ace5ad7 100644 --- a/community/hooks.py +++ b/community/hooks.py @@ -136,6 +136,7 @@ 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"}, diff --git a/community/lms/api.py b/community/lms/api.py index 3a7bd1b0..9cf2fc9d 100644 --- a/community/lms/api.py +++ b/community/lms/api.py @@ -34,3 +34,21 @@ def submit_solution(exercise, code): return doc = ex.submit(code) return {"name": doc.name, "creation": doc.creation} + +@frappe.whitelist() +def save_current_lesson(batch_name, lesson_name): + """Saves the current lesson for a student/mentor. + """ + name = frappe.get_value( + doctype="LMS Batch Membership", + filters={ + "batch": batch_name, + "member_email": frappe.session.user + }, + fieldname="name") + if not name: + return + doc = frappe.get_doc("LMS Batch Membership", name) + doc.current_lesson = lesson_name + doc.save(ignore_permissions=True) + return {"current_lesson": doc.current_lesson} diff --git a/community/lms/doctype/lms_batch/lms_batch.json b/community/lms/doctype/lms_batch/lms_batch.json index 6eb5056e..9055db0d 100644 --- a/community/lms/doctype/lms_batch/lms_batch.json +++ b/community/lms/doctype/lms_batch/lms_batch.json @@ -6,11 +6,15 @@ "engine": "InnoDB", "field_order": [ "course", + "telegram_link", + "code", + "column_break_3", "title", + "video_call_link", + "batch_schedule_section", "start_date", "start_time", - "column_break_3", - "code", + "column_break_10", "sessions_on", "end_time", "section_break_5", @@ -84,7 +88,8 @@ }, { "fieldname": "section_break_5", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Batch Description" }, { "fieldname": "column_break_9", @@ -92,7 +97,8 @@ }, { "fieldname": "section_break_7", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Batch Settings" }, { "fieldname": "start_date", @@ -116,6 +122,25 @@ "fieldtype": "Time", "in_list_view": 1, "label": "End Time" + }, + { + "fieldname": "telegram_link", + "fieldtype": "Data", + "label": "Telegram Link" + }, + { + "fieldname": "video_call_link", + "fieldtype": "Data", + "label": "Video Call Link" + }, + { + "fieldname": "batch_schedule_section", + "fieldtype": "Section Break", + "label": "Batch Schedule" + }, + { + "fieldname": "column_break_10", + "fieldtype": "Column Break" } ], "index_web_pages_for_search": 1, @@ -126,7 +151,7 @@ "link_fieldname": "batch" } ], - "modified": "2021-05-06 05:46:38.469120", + "modified": "2021-05-25 18:28:01.718521", "modified_by": "Administrator", "module": "LMS", "name": "LMS Batch", diff --git a/community/lms/doctype/lms_batch/lms_batch.py b/community/lms/doctype/lms_batch/lms_batch.py index 5deb770f..5c12d0f5 100644 --- a/community/lms/doctype/lms_batch/lms_batch.py +++ b/community/lms/doctype/lms_batch/lms_batch.py @@ -17,7 +17,7 @@ class LMSBatch(Document): def validate_if_mentor(self): course = frappe.get_doc("LMS Course", self.course) - if not course.is_mentor(frappe.session.user): + if not course.is_mentor(frappe.session.user) and self.is_new(): frappe.throw(_("You are not a mentor of the course {0}").format(course.title)) def after_insert(self): @@ -69,6 +69,29 @@ class LMSBatch(Document): message.is_author = True return messages + def get_membership(self, email): + """Returns the membership document of given user. + """ + name = frappe.get_value( + doctype="LMS Batch Membership", + filters={ + "batch": self.name, + "member": email + }, + fieldname="name") + return frappe.get_doc("LMS Batch Membership", name) + + def get_current_lesson(self, user): + """Returns the name of the current lesson for the given user. + """ + 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.js b/community/lms/doctype/lms_batch_membership/lms_batch_membership.js index dfa9ab2d..0a20239d 100644 --- a/community/lms/doctype/lms_batch_membership/lms_batch_membership.js +++ b/community/lms/doctype/lms_batch_membership/lms_batch_membership.js @@ -2,7 +2,13 @@ // For license information, please see license.txt frappe.ui.form.on('LMS Batch Membership', { - // refresh: function(frm) { - - // } + onload: function(frm) { + frm.set_query('member', function(doc) { + return { + filters: { + "ignore_user_type": 1, + } + }; + }); + }, }); 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 e6aa562d..68d2eaed 100644 --- a/community/lms/doctype/lms_batch_membership/lms_batch_membership.json +++ b/community/lms/doctype/lms_batch_membership/lms_batch_membership.json @@ -8,11 +8,12 @@ "batch", "member", "member_name", - "member_email", + "member_username", "column_break_3", "course", "member_type", - "role" + "role", + "current_lesson" ], "fields": [ { @@ -59,15 +60,6 @@ "fieldname": "column_break_3", "fieldtype": "Column Break" }, - { - "fetch_from": "member.email", - "fieldname": "member_email", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Member Email", - "read_only": 1, - "read_only_depends_on": "member.email" - }, { "fetch_from": "batch.course", "fieldname": "course", @@ -75,11 +67,24 @@ "in_list_view": 1, "label": "Course", "read_only": 1 + }, + { + "fieldname": "current_lesson", + "fieldtype": "Link", + "label": "Current Lesson", + "options": "Lesson" + }, + { + "fetch_from": "member.username", + "fieldname": "member_username", + "fieldtype": "Data", + "label": "Memeber Username", + "read_only": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-05-24 09:32:04.128620", + "modified": "2021-05-24 12:40:57.125694", "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 5f4ac4d5..f50b7501 100644 --- a/community/lms/doctype/lms_batch_membership/lms_batch_membership.py +++ b/community/lms/doctype/lms_batch_membership/lms_batch_membership.py @@ -17,7 +17,8 @@ class LMSBatchMembership(Document): previous_membership = frappe.db.get_value("LMS Batch Membership", filters={ "member": self.member, - "batch": self.batch, "name": ["!=", self.name] + "batch": self.batch, + "name": ["!=", self.name] }, fieldname=["member_type","member"], as_dict=1) @@ -30,7 +31,8 @@ class LMSBatchMembership(Document): course = frappe.db.get_value("LMS Batch", self.batch, "course") previous_membership = frappe.get_all("LMS Batch Membership", filters={ - "member": self.member + "member": self.member, + "name": ["!=", self.name] }, fields=["batch", "member_type", "name"] ) diff --git a/community/lms/doctype/lms_batch_membership/test_lms_batch_membership.py b/community/lms/doctype/lms_batch_membership/test_lms_batch_membership.py index 43ceaccf..c0385c17 100644 --- a/community/lms/doctype/lms_batch_membership/test_lms_batch_membership.py +++ b/community/lms/doctype/lms_batch_membership/test_lms_batch_membership.py @@ -3,8 +3,76 @@ # See license.txt from __future__ import unicode_literals -# import frappe +import frappe import unittest class TestLMSBatchMembership(unittest.TestCase): - pass + def setUp(self): + frappe.db.sql("DELETE FROM `tabLMS Batch Membership`") + frappe.db.sql("DELETE FROM `tabLMS Batch`") + frappe.db.sql('delete from `tabLMS Course Mentor Mapping`') + frappe.db.sql("DELETE FROM `tabLMS Course`") + frappe.db.sql("DELETE FROM `tabUser` where email like '%@test.com'") + + def new_course_batch(self): + course = frappe.get_doc({ + "doctype": "LMS Course", + "name": "test-course", + "title": "Test Course", + "short_code": "XX" + }) + course.insert() + + self.new_user("mentor@test.com", "Test Mentor") + # without this, the creating batch will fail + course.add_mentor("mentor@test.com") + + frappe.session.user = "mentor@test.com" + + batch = frappe.get_doc({ + "doctype": "LMS Batch", + "name": "test-batch", + "title": "Test Batch", + "course": course.name + }) + batch.insert(ignore_permissions=True) + + frappe.session.user = "Administrator" + return course, batch + + def new_user(self, email="test@test.com", full_name="Test User"): + user = frappe.get_doc({ + "doctype": "User", + "name": email, + "email": email, + "first_name": full_name, + }) + user.insert() + return user + + def add_membership(self, batch_name, member_name, member_type="Student"): + doc = frappe.get_doc({ + "doctype": "LMS Batch Membership", + "batch": batch_name, + "member": member_name, + "member_type": member_type + }) + doc.insert() + return doc + + def test_membership(self): + course, batch = self.new_course_batch() + member = self.new_user("test01@test.com") + membership = self.add_membership(batch.name, member.name) + + assert membership.course == course.name + assert membership.member_name == member.full_name + + def test_membership_change_role(self): + course, batch = self.new_course_batch() + member = self.new_user("test01@test.com") + membership = self.add_membership(batch.name, member.name) + + # it should be possible to change role + membership.role = "Admin" + membership.save() diff --git a/community/lms/doctype/lms_course/lms_course.py b/community/lms/doctype/lms_course/lms_course.py index 501f5341..f72264ba 100644 --- a/community/lms/doctype/lms_course/lms_course.py +++ b/community/lms/doctype/lms_course/lms_course.py @@ -150,6 +150,13 @@ class LMSCourse(Document): "name") return lesson_name and frappe.get_doc("Lesson", lesson_name) + def get_lesson_index(self, lesson_name): + """Returns the {chapter_index}.{lesson_index} for the lesson. + """ + lesson = frappe.get_doc("Lesson", lesson_name) + chapter = frappe.get_doc("Chapter", lesson.chapter) + return f"{chapter.index_}.{lesson.index_}" + def get_outline(self): return CourseOutline(self) diff --git a/community/lms/doctype/lms_message/lms_message.py b/community/lms/doctype/lms_message/lms_message.py index f19d80ea..dac39033 100644 --- a/community/lms/doctype/lms_message/lms_message.py +++ b/community/lms/doctype/lms_message/lms_message.py @@ -18,7 +18,7 @@ class LMSMessage(Document): template = self.get_message_template() message = frappe._dict({ "author_name": self.author_name, - "message_time": frappe.utils.pretty_date(self.creation), + "message_time": frappe.utils.format_datetime(self.creation, "dd-mm-yyyy HH:mm"), "message": frappe.utils.md_to_html(self.message) }) @@ -32,14 +32,16 @@ class LMSMessage(Document): template = frappe.render_template(template, {{ "message": message }}) - $(".message-section").append(template); + $(".messages").append(template); + var message_element = document.getElementsByClassName("messages")[0] + message_element.scrollTo(0, message_element.scrollHeight); """.format(template, message, self.owner) frappe.publish_realtime(event="eval_js", message=js, after_commit=True) def get_message_template(self): return """ -
+
  • {{ message.author_name }} @@ -51,7 +53,7 @@ class LMSMessage(Document):
    {{ message.message }}
    -
    +
  • """ def send_email(self): diff --git a/community/lms/widgets/BatchHeader.html b/community/lms/widgets/BatchHeader.html index 1658d327..a6c99cdc 100644 --- a/community/lms/widgets/BatchHeader.html +++ b/community/lms/widgets/BatchHeader.html @@ -1,4 +1,4 @@ -
    -

    {{course_name}}

    +
    +

    {{batch_name}}

    {{member_count}} members
    diff --git a/community/lms/widgets/BatchTabs.html b/community/lms/widgets/BatchTabs.html index 42264f55..cf3c8518 100644 --- a/community/lms/widgets/BatchTabs.html +++ b/community/lms/widgets/BatchTabs.html @@ -1,22 +1,25 @@ -
    - Courses / {{ course.title }} +
    + Courses /{% if course.is_mentor(frappe.session.user) %} {{ course.title }} {% else %} {{ course.title }} {% endif %}
    {% endblock %} @@ -35,25 +38,3 @@

    About the Course

    {{frappe.utils.md_to_html(course.description)}}
    {% endmacro %} - -{% macro BatchDetails(batch) %} -

    About the Batch

    - -
    -
    -
    Session every {{batch.sessions_on}}
    -
    {{frappe.utils.format_time(batch.start_time, "short")}} - - {{frappe.utils.format_time(batch.end_time, "short")}} -
    -
    Starting {{frappe.utils.format_date(batch.start_date, "medium")}}
    -
    mentors
    - - {% for m in batch.get_mentors() %} -
    - {{ widgets.Avatar(member=m, avatar_class="avatar-medium" ) }} - {{m.full_name}} -
    - {% endfor %} -
    -
    -{% endmacro %} diff --git a/community/www/batch/about.py b/community/www/batch/about.py index b847d8c8..48be81c9 100644 --- a/community/www/batch/about.py +++ b/community/www/batch/about.py @@ -3,5 +3,3 @@ from . import utils def get_context(context): utils.get_common_context(context) - - print("context", context) diff --git a/community/www/batch/discuss.html b/community/www/batch/discuss.html index ce73e30e..74f9bb7c 100644 --- a/community/www/batch/discuss.html +++ b/community/www/batch/discuss.html @@ -12,11 +12,11 @@
    {{ widgets.BatchTabs(course=course, batch=batch) }} - {{ widgets.BatchHeader(course_name=course.title, member_count=member_count)}} -
    -
    +
    + {{ widgets.BatchHeader(batch_name=batch.title, member_count=member_count)}} +
      {{ Messages(messages) }} -
    + {{ TextArea() }}
    @@ -24,25 +24,25 @@ {% macro Messages(messages) %} {% for message in messages %} -
    +
  • {{ message.author_name }}
    - {{ frappe.utils.pretty_date(message.creation) }} + {{ frappe.utils.format_datetime(message.creation, "dd-mm-yyyy HH:mm") }}
    {{ message.message }}
    -
  • + {% endfor %} {% endmacro %} {% macro TextArea() %} -
    + - +
    {% endmacro %} diff --git a/community/www/batch/discuss.js b/community/www/batch/discuss.js index 74f435c1..4a3adc80 100644 --- a/community/www/batch/discuss.js +++ b/community/www/batch/discuss.js @@ -11,7 +11,9 @@ frappe.ready(() => { }) setTimeout(() => { - window.scrollTo(0, document.body.scrollHeight); + var message_element = document.getElementsByClassName("messages")[0] + message_element.scrollTo(0, message_element.scrollHeight); + document.getElementsByClassName("messages-container")[0].scrollIntoView({block: "center"}) }, 300); $(".msger-send-btn").click((e) => { diff --git a/community/www/batch/home.html b/community/www/batch/home.html new file mode 100644 index 00000000..89cfbf97 --- /dev/null +++ b/community/www/batch/home.html @@ -0,0 +1,57 @@ +{% extends "templates/base.html" %} +{% block title %}About{% endblock %} +{% block head_include %} + + + + +{% endblock %} + +{% block content %} +
    + {{ widgets.BatchTabs(course=course, batch=batch) }} +

    {{ batch.title }}

    +
    + {{ widgets.CourseOutline(course=course, batch=batch, show_link=True) }} +
    +
    +

    Batch Schedule

    + {{ BatchDetails(batch) }} +
    +

    Batch Details

    + {{ frappe.utils.md_to_html(batch.description) }} + {{ ConnectWithBatch(batch) }} + + +
    +{% endblock %} + +{% macro BatchDetails(batch) %} +
    +
    +
    Session every {{batch.sessions_on}}
    +
    {{frappe.utils.format_time(batch.start_time, "short")}} - + {{frappe.utils.format_time(batch.end_time, "short")}} +
    +
    Starting {{frappe.utils.format_date(batch.start_date, "medium")}}
    +
    mentors
    + + {% for m in batch.get_mentors() %} +
    + {{ widgets.Avatar(member=m, avatar_class="avatar-medium" ) }} + {{m.full_name}} +
    + {% endfor %} +
    +
    +{% endmacro %} + +{% macro ConnectWithBatch(batch)%} +

    Connect with your Batch

    +

    + Reach out on Telegram. +

    +

    + Join the Online Class. +

    +{% endmacro %} diff --git a/community/www/batch/home.py b/community/www/batch/home.py new file mode 100644 index 00000000..48be81c9 --- /dev/null +++ b/community/www/batch/home.py @@ -0,0 +1,5 @@ +import frappe +from . import utils + +def get_context(context): + utils.get_common_context(context) diff --git a/community/www/batch/learn.html b/community/www/batch/learn.html index d8f9ba36..36136ddb 100644 --- a/community/www/batch/learn.html +++ b/community/www/batch/learn.html @@ -86,49 +86,16 @@ {{ super() }} {{ LiveCodeEditorJS() }} - - + + {%- endblock %} diff --git a/community/www/batch/learn.py b/community/www/batch/learn.py index 08d6389d..4f031eab 100644 --- a/community/www/batch/learn.py +++ b/community/www/batch/learn.py @@ -10,28 +10,25 @@ def get_context(context): lesson_number = f"{chapter_index}.{lesson_index}" course_name = context.course.name - batch_name = context.batch.name if not chapter_index or not lesson_index: - frappe.local.flags.redirect_location = f"/courses/{course_name}/{batch_name}/learn/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_) raise frappe.Redirect context.lesson = context.course.get_lesson(chapter_index, lesson_index) context.lesson_index = lesson_index context.chapter_index = chapter_index - print(context.lesson) + outline = context.course.get_outline() prev_ = outline.get_prev(lesson_number) 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 = get_learn_url(course_name, batch_name, next_) - context.prev_url = get_learn_url(course_name, batch_name, prev_) + context.next_url = context.batch.get_learn_url(next_) + context.prev_url = context.batch.get_learn_url(prev_) + -def get_learn_url(course_name, batch_name, lesson_number): - if not lesson_number: - return - return f"/courses/{course_name}/{batch_name}/learn/{lesson_number}" def get_chapter_title(course_name, lesson_number): if not lesson_number: @@ -40,3 +37,9 @@ def get_chapter_title(course_name, lesson_number): lesson_index = lesson_number.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") + +def get_lesson_index(course, batch, user): + lesson = batch.get_current_lesson(user) + return lesson and course.get_lesson_index(lesson) + + diff --git a/community/www/batch/members.html b/community/www/batch/members.html index 4aae9ddc..035e12dd 100644 --- a/community/www/batch/members.html +++ b/community/www/batch/members.html @@ -17,25 +17,31 @@ {% macro MembersList(members) %} -
    +
    {% for member in members %} -
    +
    {{ widgets.Avatar(member=member, avatar_class="avatar-large") }}
    -
    - -

    {{ member.full_name }}

    -
    - {% if course.is_mentor(member.name) %} -
    Mentor
    - {% endif %} - {% if member.bio %} -
    - {{ member.bio }} +
    +
    + +

    {{ member.full_name }}

    +
    + {% if course.is_mentor(member.name) %} +
    +
    Mentor
    +
    + {% endif %}
    + {% if member.bio %} + {{member.bio}} {% endif %}
    +
    + {% if loop.index != member_count %} +
    + {% endif %} {% endfor %}
    {% endmacro %} diff --git a/community/www/batch/members.py b/community/www/batch/members.py index 48be81c9..bdf6e2c3 100644 --- a/community/www/batch/members.py +++ b/community/www/batch/members.py @@ -3,3 +3,4 @@ from . import utils def get_context(context): utils.get_common_context(context) + print(context.members[0].bio) diff --git a/community/www/batch/progress.py b/community/www/batch/progress.py index 628b6233..5556d03d 100644 --- a/community/www/batch/progress.py +++ b/community/www/batch/progress.py @@ -32,8 +32,7 @@ class BatchReport: def get_submissions(batch): students = batch.get_students() - 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()) sql = """ diff --git a/community/www/batch/utils.py b/community/www/batch/utils.py index 8f375248..24b5dd32 100644 --- a/community/www/batch/utils.py +++ b/community/www/batch/utils.py @@ -13,7 +13,7 @@ def get_common_context(context): return batch = course.get_batch(batch_name) - if not batch: + if not batch or not batch.is_member(frappe.session.user): frappe.local.flags.redirect_location = "/courses/" + course_name raise frappe.Redirect diff --git a/community/www/courses/course.html b/community/www/courses/course.html index 66a21926..a375787a 100644 --- a/community/www/courses/course.html +++ b/community/www/courses/course.html @@ -1,5 +1,5 @@ {% extends "templates/base.html" %} -{% from "www/macros/common_macro.html" import InstructorsSection, MentorsSection %} +{% from "www/macros/common_macro.html" import MentorsSection %} {% block title %}{{ course.title }}{% endblock %} {% block head_include %} @@ -23,12 +23,12 @@ {{ CourseDescription(course) }} {{ BatchSection(course) }} - {{ CourseOutline(course) }} + {{ widgets.CourseOutline(course=course, show_link=False) }}