From 34870b46256b6bf1e863fe46a8d1cf0364770a75 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Tue, 28 Feb 2023 09:19:37 +0530 Subject: [PATCH 01/13] feat: live class --- lms/lms/doctype/lms_class/lms_class.py | 22 +++++++ lms/lms/doctype/zoom_settings/__init__.py | 0 .../zoom_settings/test_zoom_settings.py | 9 +++ .../doctype/zoom_settings/zoom_settings.js | 8 +++ .../doctype/zoom_settings/zoom_settings.json | 65 +++++++++++++++++++ .../doctype/zoom_settings/zoom_settings.py | 9 +++ lms/lms/md.py | 2 +- lms/www/classes/class.html | 21 ++++++ lms/www/classes/class.js | 18 +++++ 9 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 lms/lms/doctype/zoom_settings/__init__.py create mode 100644 lms/lms/doctype/zoom_settings/test_zoom_settings.py create mode 100644 lms/lms/doctype/zoom_settings/zoom_settings.js create mode 100644 lms/lms/doctype/zoom_settings/zoom_settings.json create mode 100644 lms/lms/doctype/zoom_settings/zoom_settings.py diff --git a/lms/lms/doctype/lms_class/lms_class.py b/lms/lms/doctype/lms_class/lms_class.py index 4a91416d..2896ffd8 100644 --- a/lms/lms/doctype/lms_class/lms_class.py +++ b/lms/lms/doctype/lms_class/lms_class.py @@ -5,6 +5,9 @@ import frappe from frappe.model.document import Document from frappe import _ from frappe.utils import cint +import requests +import urllib +from requests.auth import HTTPBasicAuth class LMSClass(Document): @@ -85,3 +88,22 @@ def update_course(class_name, course, value): else: frappe.db.delete("Class Course", {"parent": class_name, "course": course}) return True + + +@frappe.whitelist() +def create_live_class(class_name): + authenticate() + + +def authenticate(): + zoom = frappe.get_single("Zoom Settings") + if not zoom.enable: + frappe.throw(_("Please enable Zoom Settings to use this feature.")) + + authenticate_url = "https://zoom.us/oauth/token?grant_type=client_credentials" + print(authenticate_url) + breakpoint + r = requests.get( + authenticate_url, auth=HTTPBasicAuth(zoom.client_id, zoom.client_secret) + ) + return r diff --git a/lms/lms/doctype/zoom_settings/__init__.py b/lms/lms/doctype/zoom_settings/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lms/lms/doctype/zoom_settings/test_zoom_settings.py b/lms/lms/doctype/zoom_settings/test_zoom_settings.py new file mode 100644 index 00000000..3162d3dc --- /dev/null +++ b/lms/lms/doctype/zoom_settings/test_zoom_settings.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestZoomSettings(FrappeTestCase): + pass diff --git a/lms/lms/doctype/zoom_settings/zoom_settings.js b/lms/lms/doctype/zoom_settings/zoom_settings.js new file mode 100644 index 00000000..70710042 --- /dev/null +++ b/lms/lms/doctype/zoom_settings/zoom_settings.js @@ -0,0 +1,8 @@ +// Copyright (c) 2023, Frappe and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Zoom Settings", { +// refresh(frm) { + +// }, +// }); diff --git a/lms/lms/doctype/zoom_settings/zoom_settings.json b/lms/lms/doctype/zoom_settings/zoom_settings.json new file mode 100644 index 00000000..df8f69b3 --- /dev/null +++ b/lms/lms/doctype/zoom_settings/zoom_settings.json @@ -0,0 +1,65 @@ +{ + "actions": [], + "creation": "2023-02-27 14:30:28.696814", + "default_view": "List", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "enable", + "sb_00", + "client_id", + "client_secret" + ], + "fields": [ + { + "default": "0", + "fieldname": "enable", + "fieldtype": "Check", + "label": "Enable" + }, + { + "depends_on": "enable", + "fieldname": "sb_00", + "fieldtype": "Section Break", + "label": "OAuth Client ID" + }, + { + "description": "The Client ID obtained from the Google Cloud Console under \n\"APIs & Services\" > \"Credentials\"\n", + "fieldname": "client_id", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Client ID", + "mandatory_depends_on": "google_drive_picker_enabled" + }, + { + "fieldname": "client_secret", + "fieldtype": "Password", + "in_list_view": 1, + "label": "Client Secret" + } + ], + "issingle": 1, + "links": [], + "modified": "2023-02-27 14:30:28.696814", + "modified_by": "Administrator", + "module": "LMS", + "name": "Zoom Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "ASC", + "states": [], + "track_changes": 1 +} \ No newline at end of file diff --git a/lms/lms/doctype/zoom_settings/zoom_settings.py b/lms/lms/doctype/zoom_settings/zoom_settings.py new file mode 100644 index 00000000..d7ed9efb --- /dev/null +++ b/lms/lms/doctype/zoom_settings/zoom_settings.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class ZoomSettings(Document): + pass diff --git a/lms/lms/md.py b/lms/lms/md.py index 1a6b9ed7..e7830722 100644 --- a/lms/lms/md.py +++ b/lms/lms/md.py @@ -114,7 +114,7 @@ def sanitize_html(html, macro): any broken tags. This makes sures that all those things are fixed before passing to the etree parser. """ - soup = BeautifulSoup(html, features="lxml") + soup = BeautifulSoup(html, features="html5lib") nodes = soup.body.children classname = "" if macro == "YouTubeVideo": diff --git a/lms/www/classes/class.html b/lms/www/classes/class.html index 87a73f2d..fb418d99 100644 --- a/lms/www/classes/class.html +++ b/lms/www/classes/class.html @@ -74,6 +74,12 @@ + +
@@ -87,6 +93,10 @@ {{ StudentsSection(class_info, class_students) }} +
+ {{ LiveClassSection(class_info) }} +
+ {% endmacro %} @@ -176,3 +186,14 @@ {% endmacro %} + + +{% macro LiveClassSection(class_info) %} +
+ {% if is_moderator %} + + {% endif %} +
+{% endmacro %} diff --git a/lms/www/classes/class.js b/lms/www/classes/class.js index 23b9c3ca..9c13ad62 100644 --- a/lms/www/classes/class.js +++ b/lms/www/classes/class.js @@ -10,6 +10,11 @@ frappe.ready(() => { $(".class-course").click((e) => { update_course(e); }); + + $("#create-live-class").click((e) => { + console.log("call"); + create_live_class(e); + }); }); const submit_student = (e) => { @@ -68,3 +73,16 @@ const update_course = (e) => { }, }); }; + +const create_live_class = (e) => { + console.log("call"); + frappe.call({ + method: "lms.lms.doctype.lms_class.lms_class.create_live_class", + args: { + class_name: $(".class-details").data("class"), + }, + callback: (data) => { + console.log(data); + }, + }); +}; From e6096bf9ed28475ef7a77610a0409cc7edb7ae47 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Thu, 2 Mar 2023 19:33:12 +0530 Subject: [PATCH 02/13] feat: live class modal --- lms/lms/doctype/lms_class/lms_class.py | 64 +++++-- lms/lms/doctype/lms_live_class/__init__.py | 0 .../doctype/lms_live_class/lms_live_class.js | 8 + .../lms_live_class/lms_live_class.json | 156 ++++++++++++++++++ .../doctype/lms_live_class/lms_live_class.py | 9 + .../lms_live_class/test_lms_live_class.py | 9 + .../doctype/zoom_settings/zoom_settings.json | 8 +- lms/public/css/style.css | 11 +- lms/www/classes/class.html | 34 +++- lms/www/classes/class.js | 77 ++++++++- 10 files changed, 357 insertions(+), 19 deletions(-) create mode 100644 lms/lms/doctype/lms_live_class/__init__.py create mode 100644 lms/lms/doctype/lms_live_class/lms_live_class.js create mode 100644 lms/lms/doctype/lms_live_class/lms_live_class.json create mode 100644 lms/lms/doctype/lms_live_class/lms_live_class.py create mode 100644 lms/lms/doctype/lms_live_class/test_lms_live_class.py diff --git a/lms/lms/doctype/lms_class/lms_class.py b/lms/lms/doctype/lms_class/lms_class.py index 2896ffd8..48e0e733 100644 --- a/lms/lms/doctype/lms_class/lms_class.py +++ b/lms/lms/doctype/lms_class/lms_class.py @@ -4,10 +4,10 @@ import frappe from frappe.model.document import Document from frappe import _ -from frappe.utils import cint +from frappe.utils import cint, format_date, format_datetime import requests -import urllib -from requests.auth import HTTPBasicAuth +import base64 +import json class LMSClass(Document): @@ -91,8 +91,40 @@ def update_course(class_name, course, value): @frappe.whitelist() -def create_live_class(class_name): - authenticate() +def create_live_class(class_name, title, duration, date, time, description=None): + date = format_date(date, "yyyy-mm-dd") + payload = { + "topic": title, + "start_time": format_datetime(f"{date} {time}", "yyyy-MM-ddTHH:mm:ssZ"), + "duration": duration, + "timezone": "IN", + "agenda": description, + "private_meeting": True, + } + headers = { + "Authorization": "Bearer " + authenticate(), + "content-type": "application/json", + } + response = requests.post( + "https://api.zoom.us/v2/users/me/meetings", headers=headers, data=json.dumps(payload) + ) + + if response.status_code == 201: + data = json.loads(response.text) + payload.update( + { + "doctype": "LMS Live Class", + "start_url": data.get("start_url"), + "join_url": data.get("join_url"), + "title": title, + "host": frappe.session.user, + "date": date, + "time": time, + } + ) + class_details = frappe.get_doc(payload) + class_details.save() + return class_details def authenticate(): @@ -100,10 +132,18 @@ def authenticate(): if not zoom.enable: frappe.throw(_("Please enable Zoom Settings to use this feature.")) - authenticate_url = "https://zoom.us/oauth/token?grant_type=client_credentials" - print(authenticate_url) - breakpoint - r = requests.get( - authenticate_url, auth=HTTPBasicAuth(zoom.client_id, zoom.client_secret) - ) - return r + authenticate_url = f"https://zoom.us/oauth/token?grant_type=account_credentials&account_id={zoom.account_id}" + + headers = { + "Authorization": "Basic " + + base64.b64encode( + bytes( + zoom.client_id + + ":" + + zoom.get_password(fieldname="client_secret", raise_exception=False), + encoding="utf8", + ) + ).decode() + } + response = requests.request("POST", authenticate_url, headers=headers) + return response.json()["access_token"] diff --git a/lms/lms/doctype/lms_live_class/__init__.py b/lms/lms/doctype/lms_live_class/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lms/lms/doctype/lms_live_class/lms_live_class.js b/lms/lms/doctype/lms_live_class/lms_live_class.js new file mode 100644 index 00000000..ebba6c7d --- /dev/null +++ b/lms/lms/doctype/lms_live_class/lms_live_class.js @@ -0,0 +1,8 @@ +// Copyright (c) 2023, Frappe and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("LMS Live Class", { +// refresh(frm) { + +// }, +// }); diff --git a/lms/lms/doctype/lms_live_class/lms_live_class.json b/lms/lms/doctype/lms_live_class/lms_live_class.json new file mode 100644 index 00000000..abecd7b0 --- /dev/null +++ b/lms/lms/doctype/lms_live_class/lms_live_class.json @@ -0,0 +1,156 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2023-03-02 10:59:01.741349", + "default_view": "List", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "host", + "class", + "password", + "column_break_astv", + "description", + "section_break_glxh", + "date", + "timezone", + "column_break_spvt", + "start_time", + "duration", + "section_break_yrpq", + "start_url", + "column_break_yokr", + "join_url" + ], + "fields": [ + { + "fieldname": "title", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Title", + "reqd": 1 + }, + { + "fieldname": "description", + "fieldtype": "Text", + "label": "Description" + }, + { + "fieldname": "date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Date", + "reqd": 1 + }, + { + "fieldname": "start_time", + "fieldtype": "Time", + "label": "Start Time", + "reqd": 1 + }, + { + "fieldname": "duration", + "fieldtype": "Int", + "label": "Duration", + "reqd": 1 + }, + { + "fieldname": "timezone", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Timezone", + "reqd": 1 + }, + { + "fieldname": "class", + "fieldtype": "Link", + "label": "Class", + "options": "LMS Class" + }, + { + "fieldname": "host", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Host", + "options": "User", + "reqd": 1 + }, + { + "fieldname": "column_break_astv", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_glxh", + "fieldtype": "Section Break", + "label": "Date and Time" + }, + { + "fieldname": "column_break_spvt", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_yrpq", + "fieldtype": "Section Break" + }, + { + "fieldname": "start_url", + "fieldtype": "Small Text", + "label": "Start URL", + "read_only": 1 + }, + { + "fieldname": "column_break_yokr", + "fieldtype": "Column Break" + }, + { + "fieldname": "join_url", + "fieldtype": "Small Text", + "label": "Join URL", + "read_only": 1 + }, + { + "fieldname": "password", + "fieldtype": "Password", + "label": "Password" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2023-03-02 17:47:07.807968", + "modified_by": "Administrator", + "module": "LMS", + "name": "LMS Live Class", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Moderator", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} \ No newline at end of file diff --git a/lms/lms/doctype/lms_live_class/lms_live_class.py b/lms/lms/doctype/lms_live_class/lms_live_class.py new file mode 100644 index 00000000..81de90d2 --- /dev/null +++ b/lms/lms/doctype/lms_live_class/lms_live_class.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class LMSLiveClass(Document): + pass diff --git a/lms/lms/doctype/lms_live_class/test_lms_live_class.py b/lms/lms/doctype/lms_live_class/test_lms_live_class.py new file mode 100644 index 00000000..b169369a --- /dev/null +++ b/lms/lms/doctype/lms_live_class/test_lms_live_class.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestLMSLiveClass(FrappeTestCase): + pass diff --git a/lms/lms/doctype/zoom_settings/zoom_settings.json b/lms/lms/doctype/zoom_settings/zoom_settings.json index df8f69b3..a20a6b5e 100644 --- a/lms/lms/doctype/zoom_settings/zoom_settings.json +++ b/lms/lms/doctype/zoom_settings/zoom_settings.json @@ -7,6 +7,7 @@ "field_order": [ "enable", "sb_00", + "account_id", "client_id", "client_secret" ], @@ -36,11 +37,16 @@ "fieldtype": "Password", "in_list_view": 1, "label": "Client Secret" + }, + { + "fieldname": "account_id", + "fieldtype": "Data", + "label": "Account ID" } ], "issingle": 1, "links": [], - "modified": "2023-02-27 14:30:28.696814", + "modified": "2023-03-01 17:15:59.722497", "modified_by": "Administrator", "module": "LMS", "name": "Zoom Settings", diff --git a/lms/public/css/style.css b/lms/public/css/style.css index 94a244cb..ede48338 100644 --- a/lms/public/css/style.css +++ b/lms/public/css/style.css @@ -1776,7 +1776,7 @@ li { } .modal-content { - font-size: var(--text-base) !important; + font-size: var(--text-sm) !important; } .modal-header, .modal-body { @@ -1790,11 +1790,14 @@ li { .modal-footer { padding: 0.75rem 1.5rem !important; border-top: none !important; - background-color: var(--gray-200) !important; + background-color: var(--gray-50) !important; + justify-content: flex-end !important; } .modal-header .modal-title { color: var(--gray-900); + line-height: 1.5rem; + margin-bottom: 0.5rem; } .frappe-chart .title { @@ -1977,3 +1980,7 @@ select { .common-page-style .tooltip-content { display: none; } + +.resize-none { + resize: none; +} diff --git a/lms/www/classes/class.html b/lms/www/classes/class.html index fb418d99..4d9788ea 100644 --- a/lms/www/classes/class.html +++ b/lms/www/classes/class.html @@ -191,9 +191,41 @@ {% macro LiveClassSection(class_info) %}
{% if is_moderator %} - + + + {% endif %}
{% endmacro %} + +{%- block script %} + {{ super() }} + {{ include_script('controls.bundle.js') }} +{% endblock %} diff --git a/lms/www/classes/class.js b/lms/www/classes/class.js index 9c13ad62..acf33173 100644 --- a/lms/www/classes/class.js +++ b/lms/www/classes/class.js @@ -11,8 +11,16 @@ frappe.ready(() => { update_course(e); }); + if ($("#live-class-form").length) { + make_live_class_form(); + } + + $("#open-class-modal").click((e) => { + e.preventDefault(); + $("#live-class-modal").modal("show"); + }); + $("#create-live-class").click((e) => { - console.log("call"); create_live_class(e); }); }); @@ -75,14 +83,77 @@ const update_course = (e) => { }; const create_live_class = (e) => { - console.log("call"); frappe.call({ method: "lms.lms.doctype.lms_class.lms_class.create_live_class", args: { class_name: $(".class-details").data("class"), + title: $("input[data-fieldname='meeting_title']").val(), + duration: $("input[data-fieldname='meeting_duration']").val(), + date: $("input[data-fieldname='meeting_date']").val(), + time: $("input[data-fieldname='meeting_time']").val(), + description: $( + "textarea[data-fieldname='meeting_description']" + ).val(), }, callback: (data) => { - console.log(data); + $("#live-class-modal").modal("hide"); }, }); }; + +const make_live_class_form = (e) => { + this.field_group = new frappe.ui.FieldGroup({ + fields: [ + { + fieldname: "meeting_title", + fieldtype: "Data", + options: "", + label: "Title", + reqd: 1, + }, + { + fieldname: "meeting_time", + fieldtype: "Datetime", + options: "", + label: "Date and Time", + reqd: 1, + }, + { + fieldname: "meeting_col", + fieldtype: "Column Break", + options: "", + }, + { + fieldname: "meeting_date", + fieldtype: "Date", + options: "", + label: "Date", + reqd: 1, + }, + { + fieldname: "meeting_duration", + fieldtype: "Int", + options: "", + label: "Duration (in Minutes)", + reqd: 1, + }, + { + fieldname: "meeting_sec", + fieldtype: "Section Break", + options: "", + }, + { + fieldname: "meeting_description", + fieldtype: "Small Text", + options: "", + max_height: 100, + min_lines: 5, + label: "Description", + }, + ], + body: $("#live-class-form").get(0), + }); + this.field_group.make(); + $("#live-class-form .form-section:last").removeClass("empty-section"); + $("#live-class-form .frappe-control").removeClass("hide-control"); +}; From bb0f3d5962cdc9642c7c02090299f6763b17a365 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Fri, 3 Mar 2023 15:15:49 +0530 Subject: [PATCH 03/13] feat: live class list display --- lms/lms/doctype/lms_class/lms_class.py | 3 + .../lms_live_class/lms_live_class.json | 16 ++-- lms/www/classes/class.html | 73 +++++++++++++------ lms/www/classes/class.js | 4 +- lms/www/classes/class.py | 10 ++- 5 files changed, 70 insertions(+), 36 deletions(-) diff --git a/lms/lms/doctype/lms_class/lms_class.py b/lms/lms/doctype/lms_class/lms_class.py index 48e0e733..43c4f505 100644 --- a/lms/lms/doctype/lms_class/lms_class.py +++ b/lms/lms/doctype/lms_class/lms_class.py @@ -120,6 +120,9 @@ def create_live_class(class_name, title, duration, date, time, description=None) "host": frappe.session.user, "date": date, "time": time, + "class": class_name, + "password": data.get("password"), + "description": description, } ) class_details = frappe.get_doc(payload) diff --git a/lms/lms/doctype/lms_live_class/lms_live_class.json b/lms/lms/doctype/lms_live_class/lms_live_class.json index abecd7b0..6c3f8bf8 100644 --- a/lms/lms/doctype/lms_live_class/lms_live_class.json +++ b/lms/lms/doctype/lms_live_class/lms_live_class.json @@ -17,7 +17,7 @@ "date", "timezone", "column_break_spvt", - "start_time", + "time", "duration", "section_break_yrpq", "start_url", @@ -44,12 +44,6 @@ "label": "Date", "reqd": 1 }, - { - "fieldname": "start_time", - "fieldtype": "Time", - "label": "Start Time", - "reqd": 1 - }, { "fieldname": "duration", "fieldtype": "Int", @@ -114,11 +108,17 @@ "fieldname": "password", "fieldtype": "Password", "label": "Password" + }, + { + "fieldname": "time", + "fieldtype": "Time", + "label": "Time", + "reqd": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2023-03-02 17:47:07.807968", + "modified": "2023-03-02 23:00:33.006661", "modified_by": "Administrator", "module": "LMS", "name": "LMS Live Class", diff --git a/lms/www/classes/class.html b/lms/www/classes/class.html index 4d9788ea..46438ceb 100644 --- a/lms/www/classes/class.html +++ b/lms/www/classes/class.html @@ -94,7 +94,7 @@
- {{ LiveClassSection(class_info) }} + {{ LiveClassSection(class_info, live_classes) }}
@@ -188,40 +188,65 @@ {% endmacro %} -{% macro LiveClassSection(class_info) %} +{% macro LiveClassSection(class_info, live_classes) %}
{% if is_moderator %} - - -
+{% endmacro %} - {% endif %} + +{% macro LiveClassList(class_info, live_classes) %} +
+ {% for class in live_classes %} +
+
+ {{ class.title }} +
+
+ {{ class.description }} +
+
+ {{ frappe.utils.format_date(class.date, "medium") }} {{ _("at") }} + {{ frappe.utils.format_time(class.time, "hh:mm a") }} +
+
+ {% endfor %}
{% endmacro %} diff --git a/lms/www/classes/class.js b/lms/www/classes/class.js index acf33173..09cfcc76 100644 --- a/lms/www/classes/class.js +++ b/lms/www/classes/class.js @@ -113,9 +113,9 @@ const make_live_class_form = (e) => { }, { fieldname: "meeting_time", - fieldtype: "Datetime", + fieldtype: "Time", options: "", - label: "Date and Time", + label: "Time", reqd: 1, }, { diff --git a/lms/www/classes/class.py b/lms/www/classes/class.py index b872685c..d5f2fd11 100644 --- a/lms/www/classes/class.py +++ b/lms/www/classes/class.py @@ -28,6 +28,14 @@ def get_context(context): "Class Student", {"parent": class_name}, ["student", "student_name", "username"] ) + context.is_moderator = has_course_moderator_role() + + context.live_classes = frappe.get_all( + "LMS Live Class", + {"class": class_name}, + ["title", "description", "time", "date", "start_url", "join_url"], + ) + for student in class_students: if student.student == frappe.session.user: session_user.append(student) @@ -38,5 +46,3 @@ def get_context(context): context.class_students = session_user + remaining_students else: context.class_students = class_students - - context.is_moderator = has_course_moderator_role() From b9c2222951d53b98b7716414219b53a9c2e5ba2b Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Mon, 6 Mar 2023 19:45:54 +0530 Subject: [PATCH 04/13] feat: create event for live class --- lms/lms/doctype/lms_class/lms_class.py | 4 +- .../lms_live_class/lms_live_class.json | 16 +++--- .../doctype/lms_live_class/lms_live_class.py | 55 ++++++++++++++++++- lms/public/css/style.css | 16 ++++++ lms/www/classes/class.html | 54 ++++++++++++------ lms/www/classes/class.js | 13 ++++- lms/www/classes/class.py | 21 ++++--- lms/www/classes/index.html | 6 +- 8 files changed, 144 insertions(+), 41 deletions(-) diff --git a/lms/lms/doctype/lms_class/lms_class.py b/lms/lms/doctype/lms_class/lms_class.py index 43c4f505..fd490dae 100644 --- a/lms/lms/doctype/lms_class/lms_class.py +++ b/lms/lms/doctype/lms_class/lms_class.py @@ -92,7 +92,7 @@ def update_course(class_name, course, value): @frappe.whitelist() def create_live_class(class_name, title, duration, date, time, description=None): - date = format_date(date, "yyyy-mm-dd") + date = format_date(date, "yyyy-mm-dd", True) payload = { "topic": title, "start_time": format_datetime(f"{date} {time}", "yyyy-MM-ddTHH:mm:ssZ"), @@ -120,7 +120,7 @@ def create_live_class(class_name, title, duration, date, time, description=None) "host": frappe.session.user, "date": date, "time": time, - "class": class_name, + "class_name": class_name, "password": data.get("password"), "description": description, } diff --git a/lms/lms/doctype/lms_live_class/lms_live_class.json b/lms/lms/doctype/lms_live_class/lms_live_class.json index 6c3f8bf8..b3ac10c1 100644 --- a/lms/lms/doctype/lms_live_class/lms_live_class.json +++ b/lms/lms/doctype/lms_live_class/lms_live_class.json @@ -9,7 +9,7 @@ "field_order": [ "title", "host", - "class", + "class_name", "password", "column_break_astv", "description", @@ -57,12 +57,6 @@ "label": "Timezone", "reqd": 1 }, - { - "fieldname": "class", - "fieldtype": "Link", - "label": "Class", - "options": "LMS Class" - }, { "fieldname": "host", "fieldtype": "Link", @@ -114,11 +108,17 @@ "fieldtype": "Time", "label": "Time", "reqd": 1 + }, + { + "fieldname": "class_name", + "fieldtype": "Link", + "label": "Class", + "options": "LMS Class" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2023-03-02 23:00:33.006661", + "modified": "2023-03-06 16:59:28.565587", "modified_by": "Administrator", "module": "LMS", "name": "LMS Live Class", diff --git a/lms/lms/doctype/lms_live_class/lms_live_class.py b/lms/lms/doctype/lms_live_class/lms_live_class.py index 81de90d2..d6a332eb 100644 --- a/lms/lms/doctype/lms_live_class/lms_live_class.py +++ b/lms/lms/doctype/lms_live_class/lms_live_class.py @@ -1,9 +1,60 @@ # Copyright (c) 2023, Frappe and contributors # For license information, please see license.txt -# import frappe +import frappe from frappe.model.document import Document class LMSLiveClass(Document): - pass + def after_insert(self): + """calendar = frappe.db.get_value( + "Google Calendar", {"user": frappe.session.user, "enable": 1}, "name" + ) + + if calendar: + event = self.create_event() + self.add_event_participants(event, calendar)""" + + def create_event(self): + event = frappe.get_doc( + { + "doctype": "Event", + "subject": f"Live Class {self.title}", + "starts_on": f"{self.date} {self.time}", + } + ) + event.save() + + return event + + def add_event_participants(self, event, calendar): + participants = frappe.get_all( + "Class Student", {"parent": self.class_name}, pluck="student" + ) + + participants.append(frappe.session.user) + print(participants) + for participant in participants: + print(participant) + frappe.get_doc( + { + "doctype": "Event Participants", + "reference_doctype": "User", + "reference_docname": participant, + "email": participant, + "parent": event.name, + "parenttype": "Event", + "parentfield": "event_participants", + } + ).save() + + event.reload() + event.update( + { + "sync_with_google_calendar": 1, + "google_calendar": calendar, + "description": f"A Live Class has been scheduled on {frappe.utils.format_date(self.date, 'medium')} at { frappe.utils.format_time(self.time, 'hh:mm a')}. Click on this link to join. {self.join_url}. {self.description}", + } + ) + + event.save() diff --git a/lms/public/css/style.css b/lms/public/css/style.css index ede48338..d0658feb 100644 --- a/lms/public/css/style.css +++ b/lms/public/css/style.css @@ -1984,3 +1984,19 @@ select { .resize-none { resize: none; } + +.lms-page-style { + background-color: var(--fg-color); + font-size: var(--text-base); +} + +.lms-card { + display: flex; + flex-direction: column; + border-width: 1px; + border-radius: 0.75rem; + border: 1px solid var(--gray-200); + padding: 1rem; + height: 100%; + position: relative; +} diff --git a/lms/www/classes/class.html b/lms/www/classes/class.html index 46438ceb..9d9199ab 100644 --- a/lms/www/classes/class.html +++ b/lms/www/classes/class.html @@ -5,10 +5,10 @@ {% block page_content %} -
+
{{ BreadCrumb(class_info) }} -
+
{{ ClassDetails(class_info) }} {{ ClassSections(class_info, class_courses, class_students, published_courses) }}
@@ -30,7 +30,11 @@ {% macro ClassDetails(class_info) %}
-
+ +
+ {{ class_info.title }} +
+
{% if class_info.start_date %} {{ frappe.utils.format_date(class_info.start_date, "medium") }} - @@ -42,11 +46,8 @@ {% endif %}
-
- {{ class_info.title }} -
{% if class_info.description %} -
+
{{ class_info.description }}
{% endif %} @@ -74,11 +75,13 @@ + {% if is_moderator or is_student %} + {% endif %} @@ -93,9 +96,11 @@ {{ StudentsSection(class_info, class_students) }}
+ {% if is_moderator or is_student %}
{{ LiveClassSection(class_info, live_classes) }}
+ {% endif %}
@@ -119,7 +124,7 @@ {% macro StudentsSection(class_info, class_students) %} -
+
{% if is_moderator %} {{ AddStudents() }} {% endif %} @@ -190,15 +195,15 @@ {% macro LiveClassSection(class_info, live_classes) %}
- {% if is_moderator %} {{ CreateLiveClass(class_info) }} - {% endif %} {{ LiveClassList(class_info, live_classes) }}
{% endmacro %} {% macro CreateLiveClass(class_info) %} + +{% if is_moderator %} @@ -228,23 +233,38 @@
+{% endif %} + {% endmacro %} {% macro LiveClassList(class_info, live_classes) %} -
+
{% for class in live_classes %} -
-
+
+
{{ class.title }}
-
- {{ class.description }} -
-
+
{{ frappe.utils.format_date(class.date, "medium") }} {{ _("at") }} {{ frappe.utils.format_time(class.time, "hh:mm a") }}
+
+ {{ class.description }} +
+ + {% if class.owner == frappe.session.user and class.date == frappe.utils.getdate() %} + + {{ _("Start Class") }} + + {% endif %} + + {% if is_student and class.date == frappe.utils.getdate() %} + + {{ _("Join Class") }} + + {% endif %} +
{% endfor %}
diff --git a/lms/www/classes/class.js b/lms/www/classes/class.js index 09cfcc76..192c3e2d 100644 --- a/lms/www/classes/class.js +++ b/lms/www/classes/class.js @@ -83,10 +83,11 @@ const update_course = (e) => { }; const create_live_class = (e) => { + let class_name = $(".class-details").data("class"); frappe.call({ method: "lms.lms.doctype.lms_class.lms_class.create_live_class", args: { - class_name: $(".class-details").data("class"), + class_name: class_name, title: $("input[data-fieldname='meeting_title']").val(), duration: $("input[data-fieldname='meeting_duration']").val(), date: $("input[data-fieldname='meeting_date']").val(), @@ -97,6 +98,16 @@ const create_live_class = (e) => { }, callback: (data) => { $("#live-class-modal").modal("hide"); + frappe.show_alert( + { + message: __("Live Class created successfully"), + indicator: "green", + }, + 3 + ); + setTimeout(function () { + window.location.href = `/classes/${class_name}#live-class`; + }, 1000); }, }); }; diff --git a/lms/www/classes/class.py b/lms/www/classes/class.py index d5f2fd11..09a98a9c 100644 --- a/lms/www/classes/class.py +++ b/lms/www/classes/class.py @@ -1,6 +1,7 @@ import frappe from lms.lms.utils import has_course_moderator_role from frappe import _ +from frappe.utils import getdate def get_context(context): @@ -28,14 +29,6 @@ def get_context(context): "Class Student", {"parent": class_name}, ["student", "student_name", "username"] ) - context.is_moderator = has_course_moderator_role() - - context.live_classes = frappe.get_all( - "LMS Live Class", - {"class": class_name}, - ["title", "description", "time", "date", "start_url", "join_url"], - ) - for student in class_students: if student.student == frappe.session.user: session_user.append(student) @@ -46,3 +39,15 @@ def get_context(context): context.class_students = session_user + remaining_students else: context.class_students = class_students + + context.is_moderator = has_course_moderator_role() + + students = [student.student for student in class_students] + context.is_student = frappe.session.user in students + + context.live_classes = frappe.get_all( + "LMS Live Class", + {"class_name": class_name, "date": [">=", getdate()]}, + ["title", "description", "time", "date", "start_url", "join_url", "owner"], + order_by="date", + ) diff --git a/lms/www/classes/index.html b/lms/www/classes/index.html index 22684ef4..74e26d29 100644 --- a/lms/www/classes/index.html +++ b/lms/www/classes/index.html @@ -4,10 +4,10 @@ {% endblock %} {% block page_content %} -
+
{% if has_course_moderator_role() %} - + {{ _("Create Class") }} {% endif %} @@ -34,7 +34,7 @@ {% set course_count = frappe.db.count("Class Course", {"parent": class.name}) %} {% set student_count = frappe.db.count("Class Student", {"parent": class.name}) %} -
+
{% if course_count %} From cb9c7966d99ecad2b06c34c1cf0ea9869cd6be87 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Wed, 8 Mar 2023 10:19:44 +0530 Subject: [PATCH 05/13] fix: uncomment live class event creation --- lms/lms/doctype/lms_live_class/lms_live_class.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lms/lms/doctype/lms_live_class/lms_live_class.py b/lms/lms/doctype/lms_live_class/lms_live_class.py index d6a332eb..d3128104 100644 --- a/lms/lms/doctype/lms_live_class/lms_live_class.py +++ b/lms/lms/doctype/lms_live_class/lms_live_class.py @@ -7,13 +7,13 @@ from frappe.model.document import Document class LMSLiveClass(Document): def after_insert(self): - """calendar = frappe.db.get_value( - "Google Calendar", {"user": frappe.session.user, "enable": 1}, "name" + calendar = frappe.db.get_value( + "Google Calendar", {"user": frappe.session.user, "enable": 1}, "name" ) if calendar: - event = self.create_event() - self.add_event_participants(event, calendar)""" + event = self.create_event() + self.add_event_participants(event, calendar) def create_event(self): event = frappe.get_doc( From 170b1b0dcc7ceb1da17f0bbc961b06ef0728a1f7 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Thu, 9 Mar 2023 21:40:49 +0530 Subject: [PATCH 06/13] fix: live class card layout --- .../doctype/lms_live_class/lms_live_class.py | 9 ++++++-- lms/public/css/style.css | 13 +++++++++-- lms/www/classes/class.html | 22 ++++++++++++++----- lms/www/classes/class.js | 2 +- 4 files changed, 36 insertions(+), 10 deletions(-) diff --git a/lms/lms/doctype/lms_live_class/lms_live_class.py b/lms/lms/doctype/lms_live_class/lms_live_class.py index d3128104..37ca1994 100644 --- a/lms/lms/doctype/lms_live_class/lms_live_class.py +++ b/lms/lms/doctype/lms_live_class/lms_live_class.py @@ -3,6 +3,8 @@ import frappe from frappe.model.document import Document +from datetime import timedelta +from frappe.utils import cint, get_datetime class LMSLiveClass(Document): @@ -16,11 +18,14 @@ class LMSLiveClass(Document): self.add_event_participants(event, calendar) def create_event(self): + start = f"{self.date} {self.time}" + event = frappe.get_doc( { "doctype": "Event", - "subject": f"Live Class {self.title}", - "starts_on": f"{self.date} {self.time}", + "subject": f"Live Class on {self.title}", + "starts_on": start, + "ends_on": get_datetime(start) + timedelta(minutes=cint(self.duration)), } ) event.save() diff --git a/lms/public/css/style.css b/lms/public/css/style.css index d0658feb..0297ffcc 100644 --- a/lms/public/css/style.css +++ b/lms/public/css/style.css @@ -1993,10 +1993,19 @@ select { .lms-card { display: flex; flex-direction: column; - border-width: 1px; border-radius: 0.75rem; - border: 1px solid var(--gray-200); + /* border: 1px solid var(--gray-200); */ + box-shadow: var(--shadow-sm); padding: 1rem; height: 100%; position: relative; } + +.live-class-panel { + margin-top: auto; + display: none; +} + +.lms-card:hover + .live-class-panel { + display: block; +} diff --git a/lms/www/classes/class.html b/lms/www/classes/class.html index 9d9199ab..f7e4bf2c 100644 --- a/lms/www/classes/class.html +++ b/lms/www/classes/class.html @@ -253,11 +253,23 @@ {{ class.description }}
- {% if class.owner == frappe.session.user and class.date == frappe.utils.getdate() %} - - {{ _("Start Class") }} - - {% endif %} +
+ {% if class.owner == frappe.session.user and class.date == frappe.utils.getdate() %} + + {{ _("Start") }} + + {% endif %} + + {% if class.owner == frappe.session.user %} + + {{ _("Edit") }} + + + + {{ _("Delete") }} + + {% endif %} +
{% if is_student and class.date == frappe.utils.getdate() %} diff --git a/lms/www/classes/class.js b/lms/www/classes/class.js index 192c3e2d..7b038376 100644 --- a/lms/www/classes/class.js +++ b/lms/www/classes/class.js @@ -107,7 +107,7 @@ const create_live_class = (e) => { ); setTimeout(function () { window.location.href = `/classes/${class_name}#live-class`; - }, 1000); + }, 2000); }, }); }; From 0593a9fb303f8729dc4c1c2e04eef6e27cd73e19 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Mon, 13 Mar 2023 15:47:51 +0530 Subject: [PATCH 07/13] fix: add student section --- lms/public/css/style.css | 13 +++++-- lms/www/classes/class.html | 57 +++++++++++++++++------------- lms/www/classes/class.js | 72 +++++++++++++++++++++++++++----------- 3 files changed, 94 insertions(+), 48 deletions(-) diff --git a/lms/public/css/style.css b/lms/public/css/style.css index 0297ffcc..ebe2bfa0 100644 --- a/lms/public/css/style.css +++ b/lms/public/css/style.css @@ -2003,9 +2003,16 @@ select { .live-class-panel { margin-top: auto; - display: none; } -.lms-card:hover + .live-class-panel { - display: block; +.lms-card .live-class-panel .btn { + visibility: hidden; } + +.lms-card:hover .live-class-panel .btn { + visibility: visible; +} + +.add-students .text-primary.link-option { + visibility: hidden; +} \ No newline at end of file diff --git a/lms/www/classes/class.html b/lms/www/classes/class.html index f7e4bf2c..0557a449 100644 --- a/lms/www/classes/class.html +++ b/lms/www/classes/class.html @@ -75,7 +75,7 @@ - {% if is_moderator or is_student %} + {% if class_students | length and (is_moderator or is_student) %}
- {% if is_moderator or is_student %} + {% if class_students | length and (is_moderator or is_student) %}
{{ LiveClassSection(class_info, live_classes) }}
@@ -175,20 +175,10 @@ {% macro AddStudents() %}
-
- {{ _("Add Student") }} -
-
-
-
- -
-
- -
+
+
{% endmacro %} @@ -254,13 +244,19 @@
- {% if is_student and class.date == frappe.utils.getdate() %} - - {{ _("Join Class") }} - - {% endif %} -
{% endfor %}
@@ -284,5 +274,22 @@ {%- block script %} {{ super() }} + + {% if is_moderator %} + + {% endif %} + {{ include_script('controls.bundle.js') }} {% endblock %} diff --git a/lms/www/classes/class.js b/lms/www/classes/class.js index 7b038376..ef6f22fd 100644 --- a/lms/www/classes/class.js +++ b/lms/www/classes/class.js @@ -15,6 +15,10 @@ frappe.ready(() => { make_live_class_form(); } + if ($(".add-students").length) { + make_add_students_section(); + } + $("#open-class-modal").click((e) => { e.preventDefault(); $("#live-class-modal").modal("show"); @@ -23,27 +27,34 @@ frappe.ready(() => { $("#create-live-class").click((e) => { create_live_class(e); }); + + setTimeout(() => { + console.log(locals.Doctype) + }, 10000); + }); const submit_student = (e) => { e.preventDefault(); - frappe.call({ - method: "lms.lms.doctype.lms_class.lms_class.add_student", - args: { - email: $("#student-email").val(), - class_name: $(".class-details").data("class"), - }, - callback: (data) => { - frappe.show_alert( - { - message: __("Student added successfully"), - indicator: "green", - }, - 3 - ); - window.location.reload(); - }, - }); + if ($('input[data-fieldname="student_input"]').val()) { + frappe.call({ + method: "lms.lms.doctype.lms_class.lms_class.add_student", + args: { + email: $('input[data-fieldname="student_input"]').val(), + class_name: $(".class-details").data("class"), + }, + callback: (data) => { + frappe.show_alert( + { + message: __("Student added successfully"), + indicator: "green", + }, + 3 + ); + window.location.reload(); + }, + }); + } }; const remove_student = (e) => { @@ -100,14 +111,14 @@ const create_live_class = (e) => { $("#live-class-modal").modal("hide"); frappe.show_alert( { - message: __("Live Class created successfully"), + message: __("Live Class added successfully"), indicator: "green", }, 3 ); setTimeout(function () { - window.location.href = `/classes/${class_name}#live-class`; - }, 2000); + window.location.reload() + }, 1000); }, }); }; @@ -168,3 +179,24 @@ const make_live_class_form = (e) => { $("#live-class-form .form-section:last").removeClass("empty-section"); $("#live-class-form .frappe-control").removeClass("hide-control"); }; + + +const make_add_students_section = () => { + this.field_group = new frappe.ui.FieldGroup({ + fields: [ + { + fieldname: "student_input", + fieldtype: "Link", + options: "User", + label: "Add Student", + filters: { + ignore_user_type: 1 + } + } + ], + body: $(".add-students").get(0) + }); + this.field_group.make(); + $(".add-students .form-section:last").removeClass("empty-section"); + $(".add-students .frappe-control").removeClass("hide-control"); +} \ No newline at end of file From d96e3f4f9f55965a75a7534271d4a4aa29daaf05 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Mon, 13 Mar 2023 18:03:03 +0530 Subject: [PATCH 08/13] fix: don't show past classes --- lms/public/css/style.css | 4 ++-- lms/www/classes/class.html | 4 ++-- lms/www/classes/index.py | 6 ++++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lms/public/css/style.css b/lms/public/css/style.css index ebe2bfa0..214e75e5 100644 --- a/lms/public/css/style.css +++ b/lms/public/css/style.css @@ -2013,6 +2013,6 @@ select { visibility: visible; } -.add-students .text-primary.link-option { - visibility: hidden; +.add-students ul li:nth-last-child(-n+2) { + display: none; } \ No newline at end of file diff --git a/lms/www/classes/class.html b/lms/www/classes/class.html index 0557a449..30b30f8e 100644 --- a/lms/www/classes/class.html +++ b/lms/www/classes/class.html @@ -245,13 +245,13 @@
{% if class.owner == frappe.session.user %} - + {{ _("Start") }} {% endif %} {% if is_student %} - + {{ _("Join Class") }} {% endif %} diff --git a/lms/www/classes/index.py b/lms/www/classes/index.py index 483b54e9..aaba98c8 100644 --- a/lms/www/classes/index.py +++ b/lms/www/classes/index.py @@ -1,10 +1,12 @@ import frappe -from frappe import _ +from frappe.utils import getdate def get_context(context): context.no_cache = 1 context.classes = frappe.get_all( - "LMS Class", fields=["name", "title", "start_date", "end_date"] + "LMS Class", { + "end_date": [">=", getdate()] + }, ["name", "title", "start_date", "end_date"] ) From 5fc879b0efce38c9aa99367d85f70fe712083b22 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Mon, 13 Mar 2023 18:14:14 +0530 Subject: [PATCH 09/13] fix: formatting --- lms/www/classes/class.html | 2 +- lms/www/classes/class.js | 16 +++++++--------- lms/www/classes/index.py | 6 +++--- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/lms/www/classes/class.html b/lms/www/classes/class.html index 30b30f8e..e0399052 100644 --- a/lms/www/classes/class.html +++ b/lms/www/classes/class.html @@ -274,7 +274,7 @@ {%- block script %} {{ super() }} - + {% if is_moderator %}