From 3e1f29af4872d531a5c4a232ba45d2622659e116 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Mon, 14 Nov 2022 16:20:13 +0530 Subject: [PATCH] feat: add and remove students and courses from class --- lms/hooks.py | 1 + .../doctype/class_course/class_course.json | 13 +- lms/lms/doctype/lms_class/lms_class.json | 26 +++- lms/lms/doctype/lms_class/lms_class.py | 45 +++++- lms/lms/web_form/class/__init__.py | 0 lms/lms/web_form/class/class.js | 3 + lms/lms/web_form/class/class.json | 87 +++++++++++ lms/lms/web_form/class/class.py | 5 + lms/public/css/style.css | 44 +++++- lms/public/js/common_functions.js | 19 +++ lms/www/classes/class.html | 142 ++++++++++++++++++ lms/www/classes/class.js | 67 +++++++++ lms/www/classes/class.py | 17 +++ lms/www/classes/index.html | 26 +++- lms/www/classes/index.py | 1 + lms/www/courses/index.js | 6 - lms/www/profiles/profile.js | 16 -- 17 files changed, 478 insertions(+), 40 deletions(-) create mode 100644 lms/lms/web_form/class/__init__.py create mode 100644 lms/lms/web_form/class/class.js create mode 100644 lms/lms/web_form/class/class.json create mode 100644 lms/lms/web_form/class/class.py create mode 100644 lms/www/classes/class.js diff --git a/lms/hooks.py b/lms/hooks.py index 8fb9cb6f..a79fc683 100644 --- a/lms/hooks.py +++ b/lms/hooks.py @@ -143,6 +143,7 @@ website_route_rules = [ }, {"from_route": "/quizzes", "to_route": "batch/quiz_list"}, {"from_route": "/quizzes/", "to_route": "batch/quiz"}, + {"from_route": "/classes/", "to_route": "classes/class"}, {"from_route": "/courses//progress", "to_route": "batch/progress"}, {"from_route": "/courses//join", "to_route": "batch/join"}, {"from_route": "/courses//manage", "to_route": "cohorts"}, diff --git a/lms/lms/doctype/class_course/class_course.json b/lms/lms/doctype/class_course/class_course.json index f5a570a2..f53619c4 100644 --- a/lms/lms/doctype/class_course/class_course.json +++ b/lms/lms/doctype/class_course/class_course.json @@ -6,7 +6,8 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "course" + "course", + "title" ], "fields": [ { @@ -16,12 +17,20 @@ "label": "Course", "options": "LMS Course", "reqd": 1 + }, + { + "fetch_from": "course.title", + "fieldname": "title", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Title", + "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-11-09 16:25:01.648986", + "modified": "2022-11-11 15:51:45.560864", "modified_by": "Administrator", "module": "LMS", "name": "Class Course", diff --git a/lms/lms/doctype/lms_class/lms_class.json b/lms/lms/doctype/lms_class/lms_class.json index d2fe0821..3611d320 100644 --- a/lms/lms/doctype/lms_class/lms_class.json +++ b/lms/lms/doctype/lms_class/lms_class.json @@ -9,7 +9,7 @@ "engine": "InnoDB", "field_order": [ "title", - "data_2", + "start_date", "end_date", "column_break_4", "description", @@ -25,11 +25,6 @@ "label": "Title", "reqd": 1 }, - { - "fieldname": "data_2", - "fieldtype": "Date", - "label": "Start Date" - }, { "fieldname": "end_date", "fieldtype": "Date", @@ -59,11 +54,16 @@ "fieldtype": "Table", "label": "Courses", "options": "Class Course" + }, + { + "fieldname": "start_date", + "fieldtype": "Date", + "label": "Start Date" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2022-11-09 16:24:28.502317", + "modified": "2022-11-11 17:41:05.472822", "modified_by": "Administrator", "module": "LMS", "name": "LMS Class", @@ -81,6 +81,18 @@ "role": "System Manager", "share": 1, "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Course Moderator", + "share": 1, + "write": 1 } ], "sort_field": "modified", diff --git a/lms/lms/doctype/lms_class/lms_class.py b/lms/lms/doctype/lms_class/lms_class.py index 46090ef9..58b26443 100644 --- a/lms/lms/doctype/lms_class/lms_class.py +++ b/lms/lms/doctype/lms_class/lms_class.py @@ -1,8 +1,51 @@ # Copyright (c) 2022, Frappe and contributors # For license information, please see license.txt -# import frappe +import frappe from frappe.model.document import Document +from frappe import _ +from frappe.utils import cint class LMSClass(Document): pass + + +@frappe.whitelist() +def add_student(email, class_name): + if not frappe.db.exists("User", email): + frappe.throw(_("There is no such user. Please create a user with this Email ID.")) + + frappe.get_doc({ + "doctype": "Class Student", + "student": email, + "student_name": frappe.db.get_value("User", email, "full_name"), + "parent": class_name, + "parenttype": "LMS Class", + "parentfield": "students" + }).save() + return True + + +@frappe.whitelist() +def remove_student(student, class_name): + frappe.db.delete("Class Student", { + "student": student, + "parent": class_name + }) + return True + + +@frappe.whitelist() +def update_course(class_name, course, value): + if cint(value): + doc = frappe.get_doc({ + "doctype": "Class Course", + "parent": class_name, + "course": course, + "parenttype": "LMS Class", + "parentfield": "courses" + }) + doc.save() + else: + frappe.db.delete("Class Course", {"parent": class_name, "course": course}) + return True diff --git a/lms/lms/web_form/class/__init__.py b/lms/lms/web_form/class/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lms/lms/web_form/class/class.js b/lms/lms/web_form/class/class.js new file mode 100644 index 00000000..699703c5 --- /dev/null +++ b/lms/lms/web_form/class/class.js @@ -0,0 +1,3 @@ +frappe.ready(function() { + // bind events here +}) \ No newline at end of file diff --git a/lms/lms/web_form/class/class.json b/lms/lms/web_form/class/class.json new file mode 100644 index 00000000..c98783da --- /dev/null +++ b/lms/lms/web_form/class/class.json @@ -0,0 +1,87 @@ +{ + "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": "2022-11-11 12:10:29.640675", + "custom_css": "", + "doc_type": "LMS Class", + "docstatus": 0, + "doctype": "Web Form", + "idx": 0, + "is_standard": 1, + "list_columns": [], + "login_required": 0, + "max_attachment_size": 0, + "modified": "2022-11-11 12:23:14.664297", + "modified_by": "Administrator", + "module": "LMS", + "name": "class", + "owner": "Administrator", + "payment_button_label": "Buy Now", + "published": 1, + "route": "class", + "show_attachments": 0, + "show_list": 0, + "show_sidebar": 0, + "success_title": "", + "success_url": "/classes", + "title": "Class", + "web_form_fields": [ + { + "allow_read_on_all_link_options": 0, + "fieldname": "title", + "fieldtype": "Data", + "hidden": 0, + "label": "Title", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 1, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "start_date", + "fieldtype": "Date", + "hidden": 0, + "label": "Start Date", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "end_date", + "fieldtype": "Date", + "hidden": 0, + "label": "End Date", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "description", + "fieldtype": "Small Text", + "hidden": 0, + "label": "Description", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + } + ] +} \ No newline at end of file diff --git a/lms/lms/web_form/class/class.py b/lms/lms/web_form/class/class.py new file mode 100644 index 00000000..e1ada619 --- /dev/null +++ b/lms/lms/web_form/class/class.py @@ -0,0 +1,5 @@ +import frappe + +def get_context(context): + # do your magic here + pass diff --git a/lms/public/css/style.css b/lms/public/css/style.css index 65ef4106..7ec1ff67 100644 --- a/lms/public/css/style.css +++ b/lms/public/css/style.css @@ -9,7 +9,21 @@ body { } input[type=checkbox] { - appearance: auto; + appearance: auto; + position: relative; + width: var(--checkbox-size)!important; + height: var(--checkbox-size); + margin-right: var(--checkbox-right-margin)!important; + background-repeat: no-repeat; + background-position: center; + border: 1px solid var(--gray-400); + box-sizing: border-box; + box-shadow: 0 1px 2px #0000001a; + border-radius: 4px; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + -webkit-print-color-adjust: exact; } .course-image { @@ -1747,10 +1761,6 @@ li { padding: 0 1.5rem !important; } -.modal-footer { - padding: 0.75rem 1.5rem !important; -} - .modal-content { font-size: var(--text-base) !important; } @@ -1764,9 +1774,9 @@ li { } .modal-footer { + padding: 0.75rem 1.5rem !important; border-top: none !important; background-color: var(--gray-200) !important; - justify-content: flex-end !important; } .modal-header .modal-title { @@ -1808,3 +1818,25 @@ select { .course-list-cta { float: right; } + +.modal-title { + font-size: var(--text-base) !important; +} + +.class-form-title { + font-size: var(--text-base); +} + +.remove-student { + cursor: pointer; +} + +.class-course-list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + grid-gap: 1rem; +} + +.class-cours { + cursor: pointer; +} diff --git a/lms/public/js/common_functions.js b/lms/public/js/common_functions.js index f60b4721..542c32e3 100644 --- a/lms/public/js/common_functions.js +++ b/lms/public/js/common_functions.js @@ -17,6 +17,14 @@ frappe.ready(() => { save_chapter(e); }); + $(".nav-link").click((e) => { + change_hash(e); + }); + + if (window.location.hash) { + open_tab(); + } + if (window.location.pathname == "/statistics") { generate_graph("New Signups", "#new-signups"); generate_graph("Course Enrollments", "#course-enrollments"); @@ -197,6 +205,7 @@ const render_chart = (data, chart_name, element, type) => { }); }; + const generate_course_completion_graph = () => { frappe.call({ method: "lms.lms.utils.get_course_completion_data", @@ -210,3 +219,13 @@ const generate_course_completion_graph = () => { }, }); }; + + +const change_hash = (e) => { + window.location.hash = $(e.currentTarget).attr("href"); +}; + + +const open_tab = () => { + $(`a[href="${window.location.hash}"]`).click(); +}; diff --git a/lms/www/classes/class.html b/lms/www/classes/class.html index e69de29b..5c21eaa4 100644 --- a/lms/www/classes/class.html +++ b/lms/www/classes/class.html @@ -0,0 +1,142 @@ +{% extends "templates/base.html" %} +{% block title %} + {{ _(class_info.title) }} +{% endblock %} + + +{% block content %} +
+
+ {{ BreadCrumb(class_info) }} +
+ {{ ClassDetails(class_info) }} + {{ ClassSections(class_info, class_courses, class_students, published_courses) }} +
+
+
+{% endblock %} + + + +{% macro BreadCrumb(class_info) %} + +{% endmacro %} + + + +{% macro ClassDetails(class_info) %} +
+
+ {{ class_info.title }} +
+
+ {{ class_info.description }} +
+
+ {% if class_info.start_date %} + + {{ frappe.utils.format_date(class_info.start_date, "medium") }} - + + {% endif %} + {% if class_info.end_date %} + + {{ frappe.utils.format_date(class_info.end_date, "medium") }} + + {% endif %} +
+
+{% endmacro %} + + + +{% macro ClassSections(class_info, class_courses, class_students, published_courses) %} +
+ + +
+ +
+
+ {{ CoursesSection(class_info, class_courses, published_courses) }} +
+ +
+ {{ StudentsSection(class_info, class_students) }} +
+ +
+
+{% endmacro %} + + +{% macro CoursesSection(class_info, class_courses, published_courses) %} +
+ {% if published_courses | length %} + {% for course in published_courses %} + {% set checked = course.name in class_courses %} + + {% endfor %} + {% endif %} +
+{% endmacro %} + + +{% macro StudentsSection(class_info, class_students) %} +
+ {{ AddStudents() }} + + {% if class_students | length %} +
+ {% for student in class_students %} +
+ {{ student.student_name }} + + + +
+ {% if not loop.last %}
{% endif %} + {% endfor %} +
+ {% else %} +

{{ _("No Students are added to this class.") }}

+ {% endif %} +
+{% endmacro %} + + +{% macro AddStudents() %} +
+
+ {{ _("Add Student") }} +
+
+
+
+ +
+
+ +
+
+{% endmacro %} diff --git a/lms/www/classes/class.js b/lms/www/classes/class.js new file mode 100644 index 00000000..3e9ce267 --- /dev/null +++ b/lms/www/classes/class.js @@ -0,0 +1,67 @@ +frappe.ready(() => { + + $("#submit-student").click((e) => { + submit_student(e); + }); + + $(".remove-student").click((e) => { + remove_student(e); + }); + + $(".class-course").click((e) => { + update_course(e); + }); + +}); + + +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(); + } + }) +}; + + +const remove_student = (e) => { + frappe.confirm("Are you sure you want to remove this student from the class?", + () => { + frappe.call({ + method: "lms.lms.doctype.lms_class.lms_class.remove_student", + args: { + "student": $(e.currentTarget).data("student"), + "class_name": $(".class-details").data("class") + }, + callback: (data) => { + frappe.show_alert({ + message: __("Student removed successfully"), + indicator: "green", + }, 3); + window.location.reload(); + } + }); + }) +} + + +const update_course = (e) => { + frappe.call({ + method: "lms.lms.doctype.lms_class.lms_class.update_course", + args: { + "course": $(e.currentTarget).data("course"), + "value": $(e.currentTarget).children("input").prop("checked") ? 1 : 0, + "class_name": $(".class-details").data("class") + } + }) +}; diff --git a/lms/www/classes/class.py b/lms/www/classes/class.py index e69de29b..7949f919 100644 --- a/lms/www/classes/class.py +++ b/lms/www/classes/class.py @@ -0,0 +1,17 @@ +import frappe + + +def get_context(context): + context.no_cache = 1 + class_name = frappe.form_dict["classname"] + + context.class_info = frappe.db.get_value("LMS Class", class_name, ["name", "title", "start_date", "end_date", "description"], as_dict=True) + context.published_courses = frappe.get_all("LMS Course", {"published": 1}, ["name", "title"]) + + context.class_courses = frappe.get_all("Class Course", { + "parent": class_name + }, pluck="course") + + context.class_students = frappe.get_all("Class Student", { + "parent": class_name + }, ["student", "student_name"]) diff --git a/lms/www/classes/index.html b/lms/www/classes/index.html index b70968c4..e9aae9ab 100644 --- a/lms/www/classes/index.html +++ b/lms/www/classes/index.html @@ -6,17 +6,39 @@ {% block content %}
- + {{ _("Create Class") }}
{{ _("All Classes") }}
{% if classes %} +
+ {% for class in classes %} +
+
+ {{ class.title }} +
+
+ {% if class.start_date %} + + {{ frappe.utils.format_date(class.start_date, "medium") }} - + + {% endif %} + {% if class.end_date %} + + {{ frappe.utils.format_date(class.end_date, "medium") }} + + {% endif %} +
+ +
+ {% endfor %} +
{% else %}
{{ _("No Classes") }}
-
{{ _("There are no classes on this site.") }}
+
{{ _("Nothing to see here.") }}
{% endif %} diff --git a/lms/www/classes/index.py b/lms/www/classes/index.py index 426be804..89108673 100644 --- a/lms/www/classes/index.py +++ b/lms/www/classes/index.py @@ -2,3 +2,4 @@ import frappe def get_context(context): context.no_cache = 1 + context.classes = frappe.get_all("LMS Class", fields=["name", "title", "start_date", "end_date"]) diff --git a/lms/www/courses/index.js b/lms/www/courses/index.js index f3bc44ec..a78fd836 100644 --- a/lms/www/courses/index.js +++ b/lms/www/courses/index.js @@ -1,11 +1,5 @@ frappe.ready(() => { - $(".nav-link").click((e) => { - change_hash(e); - }); - if (window.location.hash) { - open_tab(); - } }); const change_hash = (e) => { diff --git a/lms/www/profiles/profile.js b/lms/www/profiles/profile.js index 9349c69b..7a5d90f3 100644 --- a/lms/www/profiles/profile.js +++ b/lms/www/profiles/profile.js @@ -4,14 +4,6 @@ frappe.ready(() => { $(".role").change((e) => { save_role(e); }); - - $(".nav-link").click((e) => { - change_hash(e); - }); - - if (window.location.hash) { - open_tab(); - } }); const make_profile_active_in_navbar = () => { @@ -46,11 +38,3 @@ const save_role = (e) => { }, }); }; - -const change_hash = (e) => { - window.location.hash = $(e.currentTarget).attr("href"); -}; - -const open_tab = () => { - $(`a[href="${window.location.hash}"]`).click(); -};