From d763dba204e008a5d8b7e1a3f82412f53838e797 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Tue, 11 Jul 2023 19:29:30 +0530 Subject: [PATCH 01/15] discussions in class --- lms/public/css/style.css | 13 ++++++------ lms/public/js/website.bundle.js | 1 + lms/www/batch/learn.html | 6 ++++-- lms/www/classes/class.html | 36 +++++++++++++++++++++++++++------ lms/www/classes/class.py | 3 +++ 5 files changed, 45 insertions(+), 14 deletions(-) diff --git a/lms/public/css/style.css b/lms/public/css/style.css index 7037a6f6..ed90ef3f 100644 --- a/lms/public/css/style.css +++ b/lms/public/css/style.css @@ -43,7 +43,7 @@ body { margin-top: 2rem; } -.frappe-control .ql-editor:not(.read-mode) { +.field-group .frappe-control .ql-editor:not(.read-mode) { background-color: #FFFFFF; } @@ -321,11 +321,12 @@ input[type=checkbox] { } .common-card-style { - display: flex; - background: #FFFFFF; - border-radius: var(--border-radius-md); - position: relative; - border: 1px solid var(--gray-300) + display: flex; + background: #FFFFFF; + border-radius: var(--border-radius-md); + position: relative; + border: 1px solid var(--gray-300); + box-shadow: var(--card-shadow); } .course-card { diff --git a/lms/public/js/website.bundle.js b/lms/public/js/website.bundle.js index 7d1dfd83..d5a2c398 100644 --- a/lms/public/js/website.bundle.js +++ b/lms/public/js/website.bundle.js @@ -1,3 +1,4 @@ import "./profile.js"; import "./common_functions.js"; import "../../../../frappe/frappe/public/js/frappe/ui/chart.js"; +import "../../../../frappe/frappe/public/js/frappe/ui/keyboard.js"; diff --git a/lms/www/batch/learn.html b/lms/www/batch/learn.html index 23003702..7c92be60 100644 --- a/lms/www/batch/learn.html +++ b/lms/www/batch/learn.html @@ -118,7 +118,9 @@ {% endif %} -
{{ frappe.utils.format_date(lesson.creation, "medium") }}
+
+ {{ frappe.utils.format_date(lesson.creation, "medium") }} +
@@ -240,9 +242,9 @@ {% endmacro %} - {%- block script %} {{ super() }} + {{ include_script('controls.bundle.js') }} + {% else %} + {% endif %} {{ include_script('controls.bundle.js') }} diff --git a/lms/www/classes/class.js b/lms/www/classes/class.js index a8a45a9e..a926036f 100644 --- a/lms/www/classes/class.js +++ b/lms/www/classes/class.js @@ -42,6 +42,14 @@ frappe.ready(() => { $(".btn-close").click((e) => { window.location.reload(); }); + + $(".btn-schedule-eval").click((e) => { + open_evaluation_form(e); + }); + + $(document).on("click", ".slot", (e) => { + mark_active_slot(e); + }); }); const create_live_class = (e) => { @@ -544,3 +552,121 @@ const remove_assessment = (e) => { }); }); }; + +const open_evaluation_form = (e) => { + this.eval_form = new frappe.ui.Dialog({ + title: __("Schedule Evaluation"), + fields: [ + { + fieldtype: "Link", + fieldname: "course", + label: __("Course"), + options: "LMS Course", + reqd: 1, + filters: { + name: ["in", courses], + }, + filter_description: " ", + }, + { + fieldtype: "Date", + fieldname: "date", + label: __("Date"), + reqd: 1, + min_date: new Date( + frappe.datetime.add_days(frappe.datetime.get_today(), 1) + ), + change: () => { + get_slots(); + }, + }, + { + fieldtype: "HTML", + fieldname: "slots", + label: __("Slots"), + }, + ], + primary_action: (values) => { + submit_evaluation_form(values); + }, + }); + this.eval_form.show(); + setTimeout(() => { + $(".modal-body").css("min-height", "300px"); + }, 1000); +}; + +const get_slots = () => { + frappe.call({ + method: "lms.lms.doctype.course_evaluator.course_evaluator.get_schedule", + args: { + course: this.eval_form.get_value("course"), + date: this.eval_form.get_value("date"), + class_name: $(".class-details").data("class"), + }, + callback: (r) => { + if (r.message) { + display_slots(r.message); + } + }, + }); +}; + +const display_slots = (slots) => { + let slot_html = ""; + let day = moment(this.eval_form.get_value("date")).format("dddd"); + + slots.forEach((slot) => { + if (slot.day == day) { + slot_html += `
+ ${moment(slot.start_time, "hh:mm").format("hh:mm a")} - + ${moment(slot.end_time, "hh:mm").format("hh:mm a")} +
`; + } + }); + + if (!slot_html) { + slot_html = ``; + } + + $("[data-fieldname='slots']").html(slot_html); +}; + +const mark_active_slot = (e) => { + $(".slot").removeClass("btn-outline-primary"); + $(e.currentTarget).addClass("btn-outline-primary"); + this.current_slot = $(e.currentTarget); +}; + +const submit_evaluation_form = (values) => { + if (!this.current_slot) { + frappe.throw(__("Please select a slot")); + } + + this.eval_form.hide(); + frappe.call({ + method: "lms.lms.doctype.lms_certificate_request.lms_certificate_request.create_certificate_request", + args: { + course: values.course, + date: values.date, + start_time: this.current_slot.data("start"), + end_time: this.current_slot.data("end"), + day: this.current_slot.data("day"), + class_name: $(".class-details").data("class"), + }, + callback: (r) => { + frappe.show_alert({ + message: __("Evaluation scheduled successfully"), + indicator: "green", + }); + setTimeout(() => { + window.location.reload(); + }, 1000); + }, + }); +}; diff --git a/lms/www/classes/class.py b/lms/www/classes/class.py index dd911d35..dfab4810 100644 --- a/lms/www/classes/class.py +++ b/lms/www/classes/class.py @@ -1,13 +1,14 @@ from frappe import _ import frappe -from frappe.utils import getdate +from frappe.utils import getdate, cint from lms.www.utils import get_assessments from lms.lms.utils import ( has_course_moderator_role, has_course_evaluator_role, - get_course_progress, + get_upcoming_evals, has_submitted_assessment, has_graded_assessment, + get_membership, ) @@ -39,14 +40,10 @@ def get_context(context): context.reference_doctype = "LMS Class" context.reference_name = class_name - context.published_courses = frappe.get_all( - "LMS Course", {"published": 1}, ["name", "title"] - ) - class_courses = frappe.get_all( "Class Course", {"parent": class_name}, - ["name", "course"], + ["name", "course", "title"], order_by="creation desc", ) @@ -65,6 +62,7 @@ def get_context(context): ) context.class_courses = get_class_course_details(class_courses) + context.course_list = [course.course for course in context.class_courses] context.all_courses = frappe.get_list( "LMS Course", fields=["name", "title"], limit_page_length=0 ) @@ -74,6 +72,10 @@ def get_context(context): class_students, class_courses, context.assessments ) context.is_student = is_student(class_students) + + context.current_student = ( + get_current_student_details(class_courses, class_name) if context.is_student else None + ) context.all_assignments = get_all_assignments(class_name) context.all_quizzes = get_all_quizzes(class_name) @@ -139,36 +141,47 @@ def get_class_student_details(class_students, class_courses, assessments): ) ) student.update(frappe.db.get_value("User", student.student, "last_active", as_dict=1)) - - courses_completed = 0 - for course in class_courses: - if get_course_progress(course.course, student.student) == 100: - courses_completed += 1 - student["courses_completed"] = courses_completed - - assessments_completed = 0 - assessments_graded = 0 - for assessment in assessments: - submission = has_submitted_assessment( - assessment.assessment_name, assessment.assessment_type, student.student - ) - if submission: - assessments_completed += 1 - - if ( - assessment.assessment_type == "LMS Assignment" - and has_graded_assessment(submission) - ): - assessments_graded += 1 - elif assessment.assessment_type == "LMS Quiz": - assessments_graded += 1 - - student["assessments_completed"] = assessments_completed - student["assessments_graded"] = assessments_graded + get_progress_info(student, class_courses) + get_assessment_info(student, assessments) return sort_students(class_students) +def get_progress_info(student, class_courses): + courses_completed = 0 + student["courses"] = frappe._dict() + for course in class_courses: + membership = get_membership(course.course, student.student) + if membership and membership.progress == 100: + courses_completed += 1 + + student["courses_completed"] = courses_completed + return student + + +def get_assessment_info(student, assessments): + assessments_completed = 0 + assessments_graded = 0 + for assessment in assessments: + submission = has_submitted_assessment( + assessment.assessment_name, assessment.assessment_type, student.student + ) + if submission: + assessments_completed += 1 + + if ( + assessment.assessment_type == "LMS Assignment" and has_graded_assessment(submission) + ): + assessments_graded += 1 + elif assessment.assessment_type == "LMS Quiz": + assessments_graded += 1 + + student["assessments_completed"] = assessments_completed + student["assessments_graded"] = assessments_graded + + return student + + def sort_students(class_students): session_user = [] remaining_students = [] @@ -188,3 +201,25 @@ def sort_students(class_students): def is_student(class_students): students = [student.student for student in class_students] return frappe.session.user in students + + +def get_current_student_details(class_courses, class_name): + student_details = frappe._dict() + student_details.courses = frappe._dict() + course_list = [course.course for course in class_courses] + + get_course_progress(class_courses, student_details) + student_details.name = frappe.session.user + student_details.assessments = get_assessments(class_name, frappe.session.user) + student_details.upcoming_evals = get_upcoming_evals(frappe.session.user, course_list) + + return student_details + + +def get_course_progress(class_courses, student_details): + for course in class_courses: + membership = get_membership(course.course, frappe.session.user) + if membership: + student_details.courses[course.course] = membership.progress + else: + student_details.courses[course.course] = 0 diff --git a/lms/www/classes/progress.html b/lms/www/classes/progress.html index 2639eda7..1c191be0 100644 --- a/lms/www/classes/progress.html +++ b/lms/www/classes/progress.html @@ -64,112 +64,13 @@ {% macro UpcomingEvals(upcoming_evals) %}
-
- {{ _("Upcoming Evaluations") }} -
- {% if upcoming_evals | length %} -
- {% for eval in upcoming_evals %} -
-
-
- {{ eval.course_title }} -
- {% if eval.google_meet_link %} - - {{ _("Join") }} - - {% endif %} -
- -
- - - - - {{ frappe.utils.format_date(eval.date, "medium") }} -  - - - {{ frappe.utils.format_time(eval.start_time, "hh:mm a") }} - -
-
- - {{ _("Evaluator") }}: - - - {{ eval.evaluator_name }} - -
-
- {% endfor %} -
- {% else %} -

{{ _("No Upcoming Evaluations") }}

- {% endif %} + {% include "lms/templates/upcoming_evals.html" %}
{% endmacro %} {% macro Assessments(class_info, student) %}
-
- {{ _("Assessments") }} -
- {% if assessments | length %} -
-
-
-
-
- {{ _("Assessment") }} -
-
- {{ _("Type") }} -
-
- {{ _("Status/Score") }} -
-
-
-
- {% for assessment in assessments %} - {% set has_access = is_moderator and assessment.submission or frappe.session.user == student.name %} -
-
- - {{ assessment.title }} - -
- {{ (assessment.assessment_type).split("LMS ")[1] }} -
- -
- {% if assessment.submission %} - {% if assessment.assessment_type == "LMS Assignment" %} - {% set status = assessment.submission.status %} - {% set color = "green" if status == "Pass" else "red" if status == "Fail" else "orange" %} -
- {{ status }} -
- {% else %} -
- {{ assessment.submission.score }} -
- {% endif %} - {% else %} -
- {{ _("Not Attempted") }} -
- {% endif %} -
- -
-
- {% endfor %} -
- {% else %} -

{{ _("No Assessments") }}

- {% endif %} + {% include "lms/templates/assessments.html" %}
{% endmacro %} diff --git a/lms/www/classes/progress.js b/lms/www/classes/progress.js index 44c4268b..6c136c9a 100644 --- a/lms/www/classes/progress.js +++ b/lms/www/classes/progress.js @@ -2,130 +2,4 @@ frappe.ready(() => { $(".clickable-row").click((e) => { window.location.href = $(e.currentTarget).data("href"); }); - - $(".btn-schedule-eval").click((e) => { - open_evaluation_form(e); - }); - - $(document).on("click", ".slot", (e) => { - mark_active_slot(e); - }); }); - -const open_evaluation_form = (e) => { - this.eval_form = new frappe.ui.Dialog({ - title: __("Schedule Evaluation"), - fields: [ - { - fieldtype: "Link", - fieldname: "course", - label: __("Course"), - options: "LMS Course", - reqd: 1, - filters: { - name: ["in", courses], - }, - filter_description: " ", - }, - { - fieldtype: "Date", - fieldname: "date", - label: __("Date"), - reqd: 1, - min_date: new Date( - frappe.datetime.add_days(frappe.datetime.get_today(), 1) - ), - change: () => { - get_slots(); - }, - }, - { - fieldtype: "HTML", - fieldname: "slots", - label: __("Slots"), - }, - ], - primary_action: (values) => { - submit_evaluation_form(values); - }, - }); - this.eval_form.show(); - setTimeout(() => { - $(".modal-body").css("min-height", "300px"); - }, 1000); -}; - -const get_slots = () => { - frappe.call({ - method: "lms.lms.doctype.course_evaluator.course_evaluator.get_schedule", - args: { - course: this.eval_form.get_value("course"), - date: this.eval_form.get_value("date"), - class_name: class_name, - }, - callback: (r) => { - if (r.message) { - display_slots(r.message); - } - }, - }); -}; - -const display_slots = (slots) => { - let slot_html = ""; - let day = moment(this.eval_form.get_value("date")).format("dddd"); - - slots.forEach((slot) => { - if (slot.day == day) { - slot_html += `
- ${moment(slot.start_time, "hh:mm").format("hh:mm a")} - - ${moment(slot.end_time, "hh:mm").format("hh:mm a")} -
`; - } - }); - - if (!slot_html) { - slot_html = ``; - } - - $("[data-fieldname='slots']").html(slot_html); -}; - -const mark_active_slot = (e) => { - $(".slot").removeClass("btn-outline-primary"); - $(e.currentTarget).addClass("btn-outline-primary"); - this.current_slot = $(e.currentTarget); -}; - -const submit_evaluation_form = (values) => { - if (!this.current_slot) { - frappe.throw(__("Please select a slot")); - } - - this.eval_form.hide(); - frappe.call({ - method: "lms.lms.doctype.lms_certificate_request.lms_certificate_request.create_certificate_request", - args: { - course: values.course, - date: values.date, - start_time: this.current_slot.data("start"), - end_time: this.current_slot.data("end"), - day: this.current_slot.data("day"), - class_name: class_name, - }, - callback: (r) => { - frappe.show_alert({ - message: __("Evaluation scheduled successfully"), - indicator: "green", - }); - setTimeout(() => { - window.location.reload(); - }, 1000); - }, - }); -}; diff --git a/lms/www/classes/progress.py b/lms/www/classes/progress.py index d273162c..90b41282 100644 --- a/lms/www/classes/progress.py +++ b/lms/www/classes/progress.py @@ -1,5 +1,9 @@ import frappe -from lms.lms.utils import has_course_moderator_role, has_course_evaluator_role +from lms.lms.utils import ( + has_course_moderator_role, + has_course_evaluator_role, + get_upcoming_evals, +) from frappe import _ from lms.www.utils import get_assessments @@ -34,20 +38,4 @@ def get_context(context): ) context.assessments = get_assessments(class_name, context.student.name) - - upcoming_evals = frappe.get_all( - "LMS Certificate Request", - { - "member": context.student.name, - "course": ["in", context.courses], - "date": [">=", frappe.utils.nowdate()], - }, - ["date", "start_time", "course", "evaluator", "google_meet_link"], - order_by="date", - ) - - for evals in upcoming_evals: - evals.course_title = frappe.db.get_value("LMS Course", evals.course, "title") - evals.evaluator_name = frappe.db.get_value("User", evals.evaluator, "full_name") - - context.upcoming_evals = upcoming_evals + context.upcoming_evals = get_upcoming_evals(context.student.name, context.courses) From 5d2b19cc439820967a612a87abd5152ac4f6bf60 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Tue, 1 Aug 2023 10:10:06 +0530 Subject: [PATCH 03/15] feat: learning flow in class --- lms/lms/doctype/lms_class/lms_class.js | 21 +++++++ lms/lms/doctype/lms_class/lms_class.json | 25 +++++++- lms/lms/doctype/lms_class/lms_class.py | 12 ++++ lms/lms/doctype/scheduled_flow/__init__.py | 0 .../scheduled_flow/scheduled_flow.json | 60 +++++++++++++++++++ .../doctype/scheduled_flow/scheduled_flow.py | 9 +++ lms/www/classes/class.html | 48 ++++++++++++++- lms/www/classes/class.py | 12 ++++ 8 files changed, 184 insertions(+), 3 deletions(-) create mode 100644 lms/lms/doctype/scheduled_flow/__init__.py create mode 100644 lms/lms/doctype/scheduled_flow/scheduled_flow.json create mode 100644 lms/lms/doctype/scheduled_flow/scheduled_flow.py diff --git a/lms/lms/doctype/lms_class/lms_class.js b/lms/lms/doctype/lms_class/lms_class.js index d3d3e2a2..a460c88e 100644 --- a/lms/lms/doctype/lms_class/lms_class.js +++ b/lms/lms/doctype/lms_class/lms_class.js @@ -11,4 +11,25 @@ frappe.ui.form.on("LMS Class", { }; }); }, + + fetch_lessons: (frm) => { + frm.clear_table("scheduled_flow"); + frappe.call({ + method: "lms.lms.doctype.lms_class.lms_class.fetch_lessons", + args: { + courses: frm.doc.courses, + }, + callback: (r) => { + if (r.message) { + console.log(r.message); + r.message.forEach((lesson) => { + console.log(typeof lesson); + let row = frm.add_child("scheduled_flow"); + row.lesson = lesson.name; + }); + frm.refresh_field("scheduled_flow"); + } + }, + }); + }, }); diff --git a/lms/lms/doctype/lms_class/lms_class.json b/lms/lms/doctype/lms_class/lms_class.json index 7253100b..39f1ed36 100644 --- a/lms/lms/doctype/lms_class/lms_class.json +++ b/lms/lms/doctype/lms_class/lms_class.json @@ -24,6 +24,10 @@ "description", "students", "courses", + "section_break_lbwu", + "fetch_lessons", + "scheduled_flow", + "section_break_ubxi", "custom_component", "assessment_tab", "assessment" @@ -134,11 +138,30 @@ "fieldname": "category", "fieldtype": "Autocomplete", "label": "Category" + }, + { + "fieldname": "section_break_lbwu", + "fieldtype": "Section Break" + }, + { + "fieldname": "scheduled_flow", + "fieldtype": "Table", + "label": "Scheduled Flow", + "options": "Scheduled Flow" + }, + { + "fieldname": "section_break_ubxi", + "fieldtype": "Section Break" + }, + { + "fieldname": "fetch_lessons", + "fieldtype": "Button", + "label": "Fetch Lessons" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2023-07-13 11:30:09.097605", + "modified": "2023-07-31 15:45:03.839896", "modified_by": "Administrator", "module": "LMS", "name": "LMS Class", diff --git a/lms/lms/doctype/lms_class/lms_class.py b/lms/lms/doctype/lms_class/lms_class.py index 862bec75..36d7d255 100644 --- a/lms/lms/doctype/lms_class/lms_class.py +++ b/lms/lms/doctype/lms_class/lms_class.py @@ -8,6 +8,7 @@ import json from frappe import _ from frappe.model.document import Document from frappe.utils import cint, format_date, format_datetime +from lms.lms.utils import get_lessons class LMSClass(Document): @@ -188,3 +189,14 @@ def create_class( ) class_details.save() return class_details + + +@frappe.whitelist() +def fetch_lessons(courses): + lessons = [] + courses = json.loads(courses) + + for course in courses: + lessons.extend(get_lessons(course.get("course"))) + + return lessons diff --git a/lms/lms/doctype/scheduled_flow/__init__.py b/lms/lms/doctype/scheduled_flow/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lms/lms/doctype/scheduled_flow/scheduled_flow.json b/lms/lms/doctype/scheduled_flow/scheduled_flow.json new file mode 100644 index 00000000..22714acf --- /dev/null +++ b/lms/lms/doctype/scheduled_flow/scheduled_flow.json @@ -0,0 +1,60 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2023-07-31 15:10:29.287475", + "default_view": "List", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "lesson", + "date", + "column_break_yikh", + "start_time", + "end_time" + ], + "fields": [ + { + "fieldname": "lesson", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Lesson", + "options": "Course Lesson", + "reqd": 1 + }, + { + "fieldname": "start_time", + "fieldtype": "Time", + "in_list_view": 1, + "label": "Start Time" + }, + { + "fieldname": "end_time", + "fieldtype": "Time", + "in_list_view": 1, + "label": "End Time" + }, + { + "fieldname": "column_break_yikh", + "fieldtype": "Column Break" + }, + { + "fieldname": "date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Date" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2023-07-31 16:53:36.829164", + "modified_by": "Administrator", + "module": "LMS", + "name": "Scheduled Flow", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/lms/lms/doctype/scheduled_flow/scheduled_flow.py b/lms/lms/doctype/scheduled_flow/scheduled_flow.py new file mode 100644 index 00000000..a712ec39 --- /dev/null +++ b/lms/lms/doctype/scheduled_flow/scheduled_flow.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 ScheduledFlow(Document): + pass diff --git a/lms/www/classes/class.html b/lms/www/classes/class.html index fd264025..c24a9a3b 100644 --- a/lms/www/classes/class.html +++ b/lms/www/classes/class.html @@ -10,7 +10,7 @@ {{ BreadCrumb(class_info) }}
{{ ClassDetails(class_info) }} - {{ ClassSections(class_info, class_courses, class_students, published_courses) }} + {{ ClassSections(class_info, class_courses, class_students, published_courses, flow) }}
@@ -82,7 +82,7 @@ -{% macro ClassSections(class_info, class_courses, class_students, published_courses) %} +{% macro ClassSections(class_info, class_courses, class_students, published_courses, flow) %} + {% if flow | length %} +
+ {{ ScheduleSection(flow) }} +
+ {% endif %} +
{{ StudentsSection(class_info, class_students) }}
@@ -450,6 +468,32 @@ {% endmacro %} + +{% macro ScheduleSection(flow) %} +
+{% endmacro %} + {%- block script %} {{ super() }} {% if is_moderator %} diff --git a/lms/www/classes/class.py b/lms/www/classes/class.py index 74b4b5a3..25dea202 100644 --- a/lms/www/classes/class.py +++ b/lms/www/classes/class.py @@ -73,6 +73,7 @@ def get_context(context): context.is_student = is_student(class_students) context.all_assignments = get_all_assignments(class_name) context.all_quizzes = get_all_quizzes(class_name) + context.flow = get_scheduled_flow(class_name) def get_all_quizzes(class_name): @@ -185,3 +186,14 @@ def sort_students(class_students): def is_student(class_students): students = [student.student for student in class_students] return frappe.session.user in students + + +def get_scheduled_flow(class_name): + lessons = frappe.get_all("Scheduled Flow", {"parent": class_name}, ["name", "lesson"]) + + for lesson in lessons: + lesson.update( + frappe.db.get_value("Course Lesson", lesson.lesson, ["body"], as_dict=True) + ) + + return lessons From 845b9068513639a777b63f0a175c1398d2d3a342 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Tue, 1 Aug 2023 18:02:02 +0530 Subject: [PATCH 04/15] feat: redirection from class flow --- lms/lms/doctype/lms_class/lms_class.js | 3 +- .../scheduled_flow/scheduled_flow.json | 12 ++++- lms/lms/widgets/CourseCard.html | 2 +- lms/lms/widgets/CourseOutline.html | 5 +- lms/public/js/common_functions.js | 24 +++------- lms/www/batch/edit.js | 2 +- lms/www/batch/learn.html | 46 ++++++++++++++----- lms/www/batch/learn.py | 10 ++++ lms/www/classes/class.html | 4 +- lms/www/classes/class.py | 12 ++++- 10 files changed, 79 insertions(+), 41 deletions(-) diff --git a/lms/lms/doctype/lms_class/lms_class.js b/lms/lms/doctype/lms_class/lms_class.js index a460c88e..7f3c4299 100644 --- a/lms/lms/doctype/lms_class/lms_class.js +++ b/lms/lms/doctype/lms_class/lms_class.js @@ -21,11 +21,10 @@ frappe.ui.form.on("LMS Class", { }, callback: (r) => { if (r.message) { - console.log(r.message); r.message.forEach((lesson) => { - console.log(typeof lesson); let row = frm.add_child("scheduled_flow"); row.lesson = lesson.name; + row.lesson_title = lesson.title; }); frm.refresh_field("scheduled_flow"); } diff --git a/lms/lms/doctype/scheduled_flow/scheduled_flow.json b/lms/lms/doctype/scheduled_flow/scheduled_flow.json index 22714acf..99ef1c33 100644 --- a/lms/lms/doctype/scheduled_flow/scheduled_flow.json +++ b/lms/lms/doctype/scheduled_flow/scheduled_flow.json @@ -8,8 +8,9 @@ "engine": "InnoDB", "field_order": [ "lesson", - "date", + "lesson_title", "column_break_yikh", + "date", "start_time", "end_time" ], @@ -43,12 +44,19 @@ "fieldtype": "Date", "in_list_view": 1, "label": "Date" + }, + { + "fetch_from": "lesson.title", + "fieldname": "lesson_title", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Lesson Title" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-07-31 16:53:36.829164", + "modified": "2023-08-01 12:31:27.512437", "modified_by": "Administrator", "module": "LMS", "name": "Scheduled Flow", diff --git a/lms/lms/widgets/CourseCard.html b/lms/lms/widgets/CourseCard.html index 486471c9..c17422bb 100644 --- a/lms/lms/widgets/CourseCard.html +++ b/lms/lms/widgets/CourseCard.html @@ -120,7 +120,7 @@ {% else %} {% if progress != 100 and membership and not course.upcoming %} - {% set lesson_index = get_lesson_index(membership.current_lesson or "1.1") %} + {% set lesson_index = get_lesson_index(membership.current_lesson) or "1.1" %} {% set query_parameter = "?batch=" + membership.batch if membership.batch else "" %} diff --git a/lms/lms/widgets/CourseOutline.html b/lms/lms/widgets/CourseOutline.html index 1ef4059b..b513ceee 100644 --- a/lms/lms/widgets/CourseOutline.html +++ b/lms/lms/widgets/CourseOutline.html @@ -57,7 +57,10 @@
{% if membership or lesson.include_in_preview or is_instructor or has_course_moderator_role() %} - diff --git a/lms/public/js/common_functions.js b/lms/public/js/common_functions.js index 2eeb43ae..fc5cead8 100644 --- a/lms/public/js/common_functions.js +++ b/lms/public/js/common_functions.js @@ -200,16 +200,8 @@ const expand_the_first_chapter = () => { }; const expand_the_active_chapter = () => { - /* Find anchor matching the URL for course details page */ - let selector = $( - `a[href="${decodeURIComponent(window.location.pathname)}"]` - ).parent(); - - if (!selector.length) { - selector = $( - `a[href^="${decodeURIComponent(window.location.pathname)}"]` - ).parent(); - } + let selector = $(".course-home-headings.title"); + console.log(selector); if (selector.length && $(".course-details-page").length) { expand_for_course_details(selector); } else if ($(".active-lesson").length) { @@ -225,15 +217,11 @@ const expand_the_active_chapter = () => { const expand_for_course_details = (selector) => { $(".lesson-info").removeClass("active-lesson"); $(".lesson-info").each((i, elem) => { - let href = $(elem).find("use").attr("href"); - href.endsWith("blue") && - $(elem) - .find("use") - .attr("href", href.substring(0, href.length - 5)); + if ($(elem).data("lesson") == selector.data("lesson")) { + $(elem).addClass("active-lesson"); + show_section($(elem).parent().parent()); + } }); - selector.addClass("active-lesson"); - - show_section(selector.parent().parent()); }; const show_section = (element) => { diff --git a/lms/www/batch/edit.js b/lms/www/batch/edit.js index 285fc2d8..dd0ae722 100644 --- a/lms/www/batch/edit.js +++ b/lms/www/batch/edit.js @@ -1,6 +1,7 @@ frappe.ready(() => { frappe.telemetry.capture("on_lesson_creation_page", "lms"); let self = this; + this.quiz_in_lesson = []; if ($("#current-lesson-content").length) { parse_string_to_lesson(); } @@ -48,7 +49,6 @@ const setup_editor = () => { const parse_string_to_lesson = () => { let lesson_content = $("#current-lesson-content").html(); let lesson_blocks = []; - this.quiz_in_lesson = []; lesson_content.split("\n").forEach((block) => { if (block.includes("{{ YouTubeVideo")) { diff --git a/lms/www/batch/learn.html b/lms/www/batch/learn.html index 23003702..f268c076 100644 --- a/lms/www/batch/learn.html +++ b/lms/www/batch/learn.html @@ -39,13 +39,14 @@ {% endif %}
- {{ widgets.CourseOutline(course=course, membership=membership, lesson_page=True) }} + {% set from_class = True if class_info else False %} + {{ widgets.CourseOutline(course=course, membership=membership, lesson_page=True, from_class=from_class) }}
- {{ BreadCrumb(course, lesson) }} - {{ LessonContent(lesson) }} - {% if course.status == "Approved" and not course.upcoming %} + {{ BreadCrumb(course, lesson, class_info) }} + {{ LessonContent(lesson, class_info) }} + {% if course.status == "Approved" and not course.upcoming and not class_info %} {{ Discussions() }} {% endif %}
@@ -56,19 +57,39 @@ -{% macro BreadCrumb(course, lesson) %} +{% macro BreadCrumb(course, lesson, class_info) %}
{% endmacro %} -{% macro LessonContent(lesson) %} +{% macro LessonContent(lesson, class_info) %} {% set instructors = get_instructors(course.name) %} {% set is_instructor = is_instructor(course.name) %} @@ -144,8 +165,9 @@ {% endif %} + {% if not class_info %} {{ pagination(prev_url, next_url) }} - + {% endif %} {% endmacro %} diff --git a/lms/www/batch/learn.py b/lms/www/batch/learn.py index 077b3ba1..fae056f7 100644 --- a/lms/www/batch/learn.py +++ b/lms/www/batch/learn.py @@ -15,6 +15,16 @@ def get_context(context): chapter_index = frappe.form_dict.get("chapter") lesson_index = frappe.form_dict.get("lesson") + class_name = frappe.form_dict.get("class") + + if class_name: + context.class_info = frappe._dict( + { + "name": class_name, + "title": frappe.db.get_value("LMS Class", class_name, "title"), + } + ) + lesson_number = f"{chapter_index}.{lesson_index}" context.lesson_number = lesson_number context.lesson_index = lesson_index diff --git a/lms/www/classes/class.html b/lms/www/classes/class.html index c24a9a3b..fca30578 100644 --- a/lms/www/classes/class.html +++ b/lms/www/classes/class.html @@ -482,8 +482,8 @@ {% for lesson in flow %} diff --git a/lms/www/classes/class.py b/lms/www/classes/class.py index 25dea202..f8380b25 100644 --- a/lms/www/classes/class.py +++ b/lms/www/classes/class.py @@ -8,6 +8,8 @@ from lms.lms.utils import ( get_course_progress, has_submitted_assessment, has_graded_assessment, + get_lesson_index, + get_lesson_url, ) @@ -189,11 +191,17 @@ def is_student(class_students): def get_scheduled_flow(class_name): - lessons = frappe.get_all("Scheduled Flow", {"parent": class_name}, ["name", "lesson"]) + lessons = frappe.get_all( + "Scheduled Flow", {"parent": class_name}, ["name", "lesson"], order_by="idx" + ) for lesson in lessons: lesson.update( - frappe.db.get_value("Course Lesson", lesson.lesson, ["body"], as_dict=True) + frappe.db.get_value( + "Course Lesson", lesson.lesson, ["title", "body", "course"], as_dict=True + ) ) + lesson["index"] = get_lesson_index(lesson.lesson) + lesson["url"] = get_lesson_url(lesson.course, lesson.index) + "?class=" + class_name return lessons From 47e254ed9b9e987166e8f9b99c8e241742f0ab24 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Mon, 7 Aug 2023 21:17:23 +0530 Subject: [PATCH 05/15] fix: flow UI and quiz progress --- .../doctype/course_lesson/course_lesson.py | 12 ++- lms/lms/doctype/lms_class/lms_class.js | 3 + .../scheduled_flow/scheduled_flow.json | 5 +- lms/lms/utils.py | 26 +++++-- lms/lms/widgets/CourseOutline.html | 5 +- lms/public/css/style.css | 73 +++++++++--------- lms/templates/quiz/quiz.js | 3 + lms/www/batch/learn.html | 4 +- lms/www/batch/learn.js | 8 +- lms/www/classes/class.html | 74 +++++++++++++++---- lms/www/classes/class.py | 55 +++++++++----- 11 files changed, 182 insertions(+), 86 deletions(-) diff --git a/lms/lms/doctype/course_lesson/course_lesson.py b/lms/lms/doctype/course_lesson/course_lesson.py index 96e593d0..9ff38e84 100644 --- a/lms/lms/doctype/course_lesson/course_lesson.py +++ b/lms/lms/doctype/course_lesson/course_lesson.py @@ -92,7 +92,17 @@ def save_progress(lesson, course, status): "LMS Batch Membership", {"member": frappe.session.user, "course": course} ) if not membership: - return + return 0 + + body = frappe.db.get_value("Course Lesson", lesson, "body") + macros = find_macros(body) + quizzes = [value for name, value in macros if name == "Quiz"] + + for quiz in quizzes: + if not frappe.db.exists( + "LMS Quiz Submission", {"quiz": quiz, "owner": frappe.session.user} + ): + return 0 filters = {"lesson": lesson, "owner": frappe.session.user, "course": course} if frappe.db.exists("LMS Course Progress", filters): diff --git a/lms/lms/doctype/lms_class/lms_class.js b/lms/lms/doctype/lms_class/lms_class.js index 7f3c4299..d8a510ef 100644 --- a/lms/lms/doctype/lms_class/lms_class.js +++ b/lms/lms/doctype/lms_class/lms_class.js @@ -21,10 +21,13 @@ frappe.ui.form.on("LMS Class", { }, callback: (r) => { if (r.message) { + let date = frappe.datetime.get_today(); r.message.forEach((lesson) => { let row = frm.add_child("scheduled_flow"); row.lesson = lesson.name; row.lesson_title = lesson.title; + row.date = date; + date = frappe.datetime.add_days(date, 1); }); frm.refresh_field("scheduled_flow"); } diff --git a/lms/lms/doctype/scheduled_flow/scheduled_flow.json b/lms/lms/doctype/scheduled_flow/scheduled_flow.json index 99ef1c33..18a602b5 100644 --- a/lms/lms/doctype/scheduled_flow/scheduled_flow.json +++ b/lms/lms/doctype/scheduled_flow/scheduled_flow.json @@ -43,7 +43,8 @@ "fieldname": "date", "fieldtype": "Date", "in_list_view": 1, - "label": "Date" + "label": "Date", + "reqd": 1 }, { "fetch_from": "lesson.title", @@ -56,7 +57,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-08-01 12:31:27.512437", + "modified": "2023-08-07 12:10:28.095018", "modified_by": "Administrator", "module": "LMS", "name": "Scheduled Flow", diff --git a/lms/lms/utils.py b/lms/lms/utils.py index db62cdcd..7bed5c92 100644 --- a/lms/lms/utils.py +++ b/lms/lms/utils.py @@ -136,18 +136,28 @@ def get_lesson_details(chapter): as_dict=True, ) lesson_details.number = flt(f"{chapter.idx}.{row.idx}") - lesson_details.icon = "icon-list" - macros = find_macros(lesson_details.body) + lesson_details.icon = get_lesson_icon(lesson_details.body) - for macro in macros: - if macro[0] == "YouTubeVideo" or macro[0] == "Video": - lesson_details.icon = "icon-youtube" - elif macro[0] == "Quiz": - lesson_details.icon = "icon-quiz" lessons.append(lesson_details) return lessons +def get_lesson_icon(content): + icon = None + macros = find_macros(content) + + for macro in macros: + if macro[0] == "YouTubeVideo" or macro[0] == "Video": + icon = "icon-youtube" + elif macro[0] == "Quiz": + icon = "icon-quiz" + + if not icon: + icon = "icon-list" + + return icon + + def get_tags(course): tags = frappe.db.get_value("LMS Course", course, "tags") return tags.split(",") if tags else [] @@ -336,7 +346,7 @@ def is_eligible_to_review(course, membership): def get_course_progress(course, member=None): """Returns the course progress of the session user""" - lesson_count = len(get_lessons(course)) + lesson_count = get_lessons(course, get_details=False) if not lesson_count: return 0 completed_lessons = frappe.db.count( diff --git a/lms/lms/widgets/CourseOutline.html b/lms/lms/widgets/CourseOutline.html index b513ceee..b95fbdba 100644 --- a/lms/lms/widgets/CourseOutline.html +++ b/lms/lms/widgets/CourseOutline.html @@ -36,7 +36,6 @@ - @@ -58,9 +57,7 @@ {% if membership or lesson.include_in_preview or is_instructor or has_course_moderator_role() %} diff --git a/lms/public/css/style.css b/lms/public/css/style.css index 7037a6f6..e3a4e0be 100644 --- a/lms/public/css/style.css +++ b/lms/public/css/style.css @@ -58,10 +58,10 @@ body { } .rating .star-click { - --star-fill: var(--orange-500); - background: var(--gray-200); - border-radius: var(--border-radius-md); - padding: var(--padding-xs); + --star-fill: var(--orange-500); + background: var(--gray-200); + border-radius: var(--border-radius-md); + padding: var(--padding-xs); } .cta-parent { @@ -80,10 +80,10 @@ body { .field-input { border: 1px solid var(--gray-300); - border-radius: var(--border-radius-md); - padding: 0.5rem; - width: 100%; - margin-top: 0.25rem; + border-radius: var(--border-radius-md); + padding: 0.5rem; + width: 100%; + margin-top: 0.25rem; } .field-input:focus-visible { @@ -151,9 +151,9 @@ textarea.field-input { } .ce-block__content { - max-width: 100%; - padding: 0 0.5rem; - margin: 0; + max-width: 100%; + padding: 0 0.5rem; + margin: 0; } .ce-toolbar__content { @@ -206,7 +206,7 @@ textarea.field-input { } .codex-editor path { - stroke: var(--gray-800); + stroke: var(--gray-800); } .drag-handle { @@ -617,19 +617,18 @@ input[type=checkbox] { } .reviews-parent { - color: var(--gray-900); + color: var(--gray-900); } .lesson-info { - font-size: 16px; - color: var(--gray-900); - letter-spacing: -0.011em; + padding: 0.5rem; + color: var(--gray-900); + letter-spacing: -0.011em; } .lesson-links { display: flex; align-items: center; - padding: 0.5rem; color: var(--gray-900); font-size: var(--text-base); } @@ -1045,42 +1044,42 @@ pre { .certificate-parent { display: grid; - grid-template-columns: 10fr 2fr; - grid-gap: 3rem; + grid-template-columns: 10fr 2fr; + grid-gap: 3rem; } .certificate-logo { - height: 1.5rem; - margin-bottom: 4rem; + height: 1.5rem; + margin-bottom: 4rem; } .certificate-name { - font-size: 2rem; - font-weight: 500; - color: #192734; - margin-bottom: 0.25rem; + font-size: 2rem; + font-weight: 500; + color: #192734; + margin-bottom: 0.25rem; } .certificate-footer { - margin: 4rem auto 0; - width: fit-content; + margin: 4rem auto 0; + width: fit-content; } .certificate-footer-item { - color: #192734; + color: #192734; } .cursive-font { - font-family: cursive; + font-family: cursive; font-weight: 600; } .certificate-divider { - margin: 0.5rem 0; + margin: 0.5rem 0; } .certificate-expiry { - margin-left: 2rem; + margin-left: 2rem; } .column-card { @@ -2121,10 +2120,10 @@ select { .lms-card { display: flex; flex-direction: column; - border-radius: 0.75rem; - border: 1px solid var(--gray-300); + border-radius: 0.75rem; + border: 1px solid var(--gray-300); /* box-shadow: var(--shadow-sm); */ - padding: 0.5rem; + padding: 0.5rem; height: 100%; position: relative; } @@ -2203,4 +2202,10 @@ select { .rows .grid-row .grid-footer-toolbar, .grid-form-heading { cursor: none; +} + +.schedule-header { + display: flex; + font-size: var(--text-sm); + padding: 0.5rem 0.5rem 0 0.5rem; } \ No newline at end of file diff --git a/lms/templates/quiz/quiz.js b/lms/templates/quiz/quiz.js index 36c2ae08..1de0b05a 100644 --- a/lms/templates/quiz/quiz.js +++ b/lms/templates/quiz/quiz.js @@ -142,6 +142,9 @@ const quiz_summary = (e = undefined) => { $("#try-again").attr("data-submission", data.message.submission); $("#try-again").removeClass("hide"); self.quiz_submitted = true; + if (this.hasOwnProperty("marked_as_complete")) { + mark_progress(); + } }, }); }; diff --git a/lms/www/batch/learn.html b/lms/www/batch/learn.html index f268c076..71d4ef9c 100644 --- a/lms/www/batch/learn.html +++ b/lms/www/batch/learn.html @@ -39,8 +39,8 @@ {% endif %}
- {% set from_class = True if class_info else False %} - {{ widgets.CourseOutline(course=course, membership=membership, lesson_page=True, from_class=from_class) }} + {% set classname = class_info.name if class_info else False %} + {{ widgets.CourseOutline(course=course, membership=membership, lesson_page=True, classname=classname) }}
diff --git a/lms/www/batch/learn.js b/lms/www/batch/learn.js index 3b1554f3..4dccb66f 100644 --- a/lms/www/batch/learn.js +++ b/lms/www/batch/learn.js @@ -15,7 +15,6 @@ frappe.ready(() => { !self.marked_as_complete && $(".title").hasClass("is-member") ) { - self.marked_as_complete = true; mark_progress(); } }); @@ -61,8 +60,11 @@ const mark_progress = () => { status: status, }, callback: (data) => { - change_progress_indicators(); - show_certificate_if_course_completed(data); + if (data.message) { + change_progress_indicators(); + show_certificate_if_course_completed(data); + self.marked_as_complete = true; + } }, }); }; diff --git a/lms/www/classes/class.html b/lms/www/classes/class.html index fca30578..0097f450 100644 --- a/lms/www/classes/class.html +++ b/lms/www/classes/class.html @@ -10,7 +10,7 @@ {{ BreadCrumb(class_info) }}
{{ ClassDetails(class_info) }} - {{ ClassSections(class_info, class_courses, class_students, published_courses, flow) }} + {{ ClassSections(class_info, class_courses, class_students, flow) }}
@@ -82,7 +82,7 @@ -{% macro ClassSections(class_info, class_courses, class_students, published_courses, flow) %} +{% macro ClassSections(class_info, class_courses, class_students, flow) %}
{% if is_moderator %} @@ -150,7 +150,7 @@
- {{ CoursesSection(class_info, class_courses, published_courses) }} + {{ CoursesSection(class_info, class_courses) }}
{% if flow | length %} @@ -180,7 +180,7 @@ {% endmacro %} -{% macro CoursesSection(class_info, class_courses, published_courses) %} +{% macro CoursesSection(class_info, class_courses) %}
@@ -476,20 +476,64 @@ {{ _("Schedule") }}
-
-
-
- {% for lesson in flow %} -
-
- - {{ lesson.title }} - + +
+ {% for chapter in flow %} +
+
+ +
+ {{ chapter.chapter_title }} +
+
+
-
+ {% endfor %}
{% endmacro %} diff --git a/lms/www/classes/class.py b/lms/www/classes/class.py index f8380b25..fe48432f 100644 --- a/lms/www/classes/class.py +++ b/lms/www/classes/class.py @@ -10,6 +10,7 @@ from lms.lms.utils import ( has_graded_assessment, get_lesson_index, get_lesson_url, + get_lesson_icon, ) @@ -38,10 +39,6 @@ def get_context(context): as_dict=True, ) - context.published_courses = frappe.get_all( - "LMS Course", {"published": 1}, ["name", "title"] - ) - class_courses = frappe.get_all( "Class Course", {"parent": class_name}, @@ -56,15 +53,8 @@ def get_context(context): order_by="creation desc", ) - 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", - ) - context.class_courses = get_class_course_details(class_courses) - context.all_courses = frappe.get_list( + context.all_courses = frappe.get_all( "LMS Course", fields=["name", "title"], limit_page_length=0 ) context.course_name_list = [course.course for course in context.class_courses] @@ -73,6 +63,17 @@ def get_context(context): class_students, class_courses, context.assessments ) context.is_student = is_student(class_students) + + if not context.is_student and not context.is_moderator and not context.is_evaluator: + raise frappe.PermissionError(_("You don't have permission to access this page.")) + + 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", + ) + context.all_assignments = get_all_assignments(class_name) context.all_quizzes = get_all_quizzes(class_name) context.flow = get_scheduled_flow(class_name) @@ -191,17 +192,37 @@ def is_student(class_students): def get_scheduled_flow(class_name): + chapters = [] + lessons = frappe.get_all( - "Scheduled Flow", {"parent": class_name}, ["name", "lesson"], order_by="idx" + "Scheduled Flow", + {"parent": class_name}, + ["name", "lesson", "date", "start_time", "end_time"], + order_by="idx", ) for lesson in lessons: lesson.update( frappe.db.get_value( - "Course Lesson", lesson.lesson, ["title", "body", "course"], as_dict=True + "Course Lesson", lesson.lesson, ["title", "body", "course", "chapter"], as_dict=True ) ) - lesson["index"] = get_lesson_index(lesson.lesson) - lesson["url"] = get_lesson_url(lesson.course, lesson.index) + "?class=" + class_name + lesson.index = get_lesson_index(lesson.lesson) + lesson.url = get_lesson_url(lesson.course, lesson.index) + "?class=" + class_name + lesson.icon = get_lesson_icon(lesson.body) - return lessons + chapter_exists = list(filter(lambda x: x.chapter == lesson.chapter, chapters)) + if len(chapter_exists) == 0: + chapters.append( + frappe._dict( + { + "chapter": lesson.chapter, + "chapter_title": frappe.db.get_value("Course Chapter", lesson.chapter, "title"), + "lessons": [lesson], + } + ) + ) + else: + chapter_exists[0]["lessons"].append(lesson) + + return chapters From 8479e90aebb039f2f6724401c3662359ebe56555 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Wed, 9 Aug 2023 10:16:48 +0530 Subject: [PATCH 06/15] fix: allow evaluators to access the discussions section --- lms/www/classes/class.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lms/www/classes/class.html b/lms/www/classes/class.html index c57e9444..1f20a6e4 100644 --- a/lms/www/classes/class.html +++ b/lms/www/classes/class.html @@ -173,7 +173,7 @@
{% endif %} - {% if class_students | length and (is_moderator or is_student) %} + {% if class_students | length and (is_moderator or is_student or is_evaluator) %}
{{ Discussions(class_info) }}
@@ -230,7 +230,7 @@ {% macro Discussions(class_info) %}
- {% set condition = is_moderator or is_student %} + {% set condition = is_moderator or is_student or is_evaluator %} {% set doctype, docname = _("LMS Class"), class_info.name %} {% set single_thread = True %} {% set title = "Discussions" %} From 6e47b4a94149324366b4572a083f63fe2e07c6c2 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Wed, 9 Aug 2023 10:53:52 +0530 Subject: [PATCH 07/15] test: fix discussions test --- cypress/e2e/course_creation.cy.js | 58 ++++++++++++++++++++++--------- lms/www/batch/learn.html | 4 ++- 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/cypress/e2e/course_creation.cy.js b/cypress/e2e/course_creation.cy.js index 3aaf6155..48f3c5dd 100644 --- a/cypress/e2e/course_creation.cy.js +++ b/cypress/e2e/course_creation.cy.js @@ -86,26 +86,50 @@ describe("Course Creation", () => { // Add Discussion cy.get(".reply").click(); cy.wait(500); - cy.get(".topic-title").type("Question Title"); - cy.get(".comment-field").type( - "Question Content. This is a very long question. It contains more than once sentence. Its meant to be this long as this is a UI test." - ); - cy.get(".submit-discussion").click(); + cy.get(".discussion-modal").should("be.visible"); - // View Discussion - cy.wait(1000); - cy.get(".discussion-topic-title:first").contains("Question Title"); - cy.get(".sidebar-parent:first").click(); - cy.get(".reply-text").contains( - "Question Content. This is a very long question. It contains more than once sentence. Its meant to be this long as this is a UI test." + // Enter title + cy.get(".modal .topic-title") + .type("Discussion from tests") + .should("have.value", "Discussion from tests"); + + // Enter comment + cy.get(".modal .discussions-comment").type( + "This is a discussion from the cypress ui tests." ); - cy.get(".comment-field:visible").type( - "This is a reply to the previous comment. Its not that long." + + // Submit + cy.get(".modal .submit-discussion").click(); + cy.wait(2000); + + // Check if discussion is added to page and content is visible + cy.get(".sidebar-parent:first .discussion-topic-title").should( + "have.text", + "Discussion from tests" ); - cy.get(".submit-discussion:visible").click(); - cy.wait(1000); - cy.get(".reply-text:last p").contains( - "This is a reply to the previous comment. Its not that long." + cy.get(".sidebar-parent:first .discussion-topic-title").click(); + cy.get(".discussion-on-page:visible").should("have.class", "show"); + cy.get( + ".discussion-on-page:visible .reply-card .reply-text .ql-editor p" + ).should( + "have.text", + "This is a discussion from the cypress ui tests." ); + + cy.get(".discussion-form:visible .discussions-comment").type( + "This is a discussion from the cypress ui tests. \n\nThis comment was entered through the commentbox on the page." + ); + + cy.get(".discussion-form:visible .submit-discussion").click(); + cy.wait(3000); + cy.get(".discussion-on-page:visible").should("have.class", "show"); + cy.get(".discussion-on-page:visible") + .children(".reply-card") + .eq(1) + .find(".reply-text") + .should( + "have.text", + "This is a discussion from the cypress ui tests. This comment was entered through the commentbox on the page.\n" + ); }); }); diff --git a/lms/www/batch/learn.html b/lms/www/batch/learn.html index 7c92be60..9c582d68 100644 --- a/lms/www/batch/learn.html +++ b/lms/www/batch/learn.html @@ -238,7 +238,9 @@ {% set redirect_to = "/courses/" + course.name %} {% set empty_state_title = _("Have a doubt?") %} {% set empty_state_subtitle = _("Post it here, our mentors will help you out.") %} - {% include "frappe/templates/discussions/discussions_section.html" %} +
+ {% include "frappe/templates/discussions/discussions_section.html" %} +
{% endmacro %} From 71dc32098ecc47b5e155cda7a79f47566b61ea69 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Wed, 9 Aug 2023 11:09:17 +0530 Subject: [PATCH 08/15] fix: removed unnecessary code --- lms/lms/utils.py | 2 -- lms/www/classes/class.html | 21 --------------------- 2 files changed, 23 deletions(-) diff --git a/lms/lms/utils.py b/lms/lms/utils.py index ecd65f35..3a2b8b1f 100644 --- a/lms/lms/utils.py +++ b/lms/lms/utils.py @@ -795,7 +795,6 @@ def get_evaluator(course, class_name=None): def get_upcoming_evals(student, courses): - print(student, courses) upcoming_evals = frappe.get_all( "LMS Certificate Request", { @@ -810,5 +809,4 @@ def get_upcoming_evals(student, courses): for evals in upcoming_evals: evals.course_title = frappe.db.get_value("LMS Course", evals.course, "title") evals.evaluator_name = frappe.db.get_value("User", evals.evaluator, "full_name") - print(upcoming_evals) return upcoming_evals diff --git a/lms/www/classes/class.html b/lms/www/classes/class.html index 1f20a6e4..336c7b91 100644 --- a/lms/www/classes/class.html +++ b/lms/www/classes/class.html @@ -205,27 +205,6 @@
{% include "lms/templates/assessments.html" %}
- {% endmacro %} {% macro Discussions(class_info) %} From 5d0a50242e3cd725311d685e3324a93590fce04a Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Wed, 9 Aug 2023 12:37:56 +0530 Subject: [PATCH 09/15] fix: progress marking for normal content --- lms/www/batch/learn.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/www/batch/learn.js b/lms/www/batch/learn.js index 4dccb66f..f7cf6a94 100644 --- a/lms/www/batch/learn.js +++ b/lms/www/batch/learn.js @@ -15,6 +15,7 @@ frappe.ready(() => { !self.marked_as_complete && $(".title").hasClass("is-member") ) { + self.marked_as_complete = true; mark_progress(); } }); @@ -63,7 +64,6 @@ const mark_progress = () => { if (data.message) { change_progress_indicators(); show_certificate_if_course_completed(data); - self.marked_as_complete = true; } }, }); From 4660240395841f3ed2462e3741c501f8c137033f Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Wed, 9 Aug 2023 14:29:51 +0530 Subject: [PATCH 10/15] fix: certificate share --- lms/install.py | 26 ++++++++++++++++--- .../lms_certificate/lms_certificate.py | 18 ++++++------- lms/www/courses/certificate.py | 7 +++-- 3 files changed, 36 insertions(+), 15 deletions(-) diff --git a/lms/install.py b/lms/install.py index fca317b0..3bd0539b 100644 --- a/lms/install.py +++ b/lms/install.py @@ -9,6 +9,7 @@ def after_install(): def after_sync(): create_lms_roles() set_default_home() + set_default_certificate_print_format() add_all_roles_to("Administrator") @@ -76,7 +77,7 @@ def create_course_creator_role(): "desk_access": 0, } ) - role.save(ignore_permissions=True) + role.save() def create_moderator_role(): @@ -89,7 +90,7 @@ def create_moderator_role(): "desk_access": 0, } ) - role.save(ignore_permissions=True) + role.save() def create_evaluator_role(): @@ -102,7 +103,26 @@ def create_evaluator_role(): "desk_access": 0, } ) - role.save(ignore_permissions=True) + role.save() + + +def set_default_certificate_print_format(): + filters = { + "doc_type": "LMS Certificate", + "property": "default_print_format", + } + if not frappe.db.exists("Property Setter", filters): + filters.update( + { + "doctype_or_field": "DocType", + "property_type": "Data", + "value": "Certificate", + } + ) + + doc = frappe.new_doc("Property Setter") + doc.update(filters) + doc.save() def delete_custom_fields(): diff --git a/lms/lms/doctype/lms_certificate/lms_certificate.py b/lms/lms/doctype/lms_certificate/lms_certificate.py index bf3fc1c0..daa400e4 100644 --- a/lms/lms/doctype/lms_certificate/lms_certificate.py +++ b/lms/lms/doctype/lms_certificate/lms_certificate.py @@ -24,17 +24,15 @@ class LMSCertificate(Document): _("{0} is already certified for the course {1}").format(full_name, course_name) ) - def after_insert(self): - share = frappe.get_doc( - { - "doctype": "DocShare", - "read": 1, - "share_doctype": "LMS Certificate", - "share_name": self.name, - "user": self.member, - } + def on_update(self): + frappe.share.add_docshare( + self.doctype, + self.name, + self.member, + write=1, + share=1, + flags={"ignore_share_permission": True}, ) - share.save(ignore_permissions=True) @frappe.whitelist() diff --git a/lms/www/courses/certificate.py b/lms/www/courses/certificate.py index 0784db9d..55144da6 100644 --- a/lms/www/courses/certificate.py +++ b/lms/www/courses/certificate.py @@ -30,7 +30,7 @@ def get_context(context): ) context.url = f"{get_url()}/courses/{context.course.name}/{context.doc.name}" - default_print_format = frappe.db.get_value( + print_format = frappe.db.get_value( "Property Setter", { "doc_type": "LMS Certificate", @@ -40,8 +40,11 @@ def get_context(context): as_dict=True, ) + if not print_format: + print_format = "Certificate" + template = frappe.db.get_value( - "Print Format", default_print_format.value, ["html", "css"], as_dict=True + "Print Format", print_format.value, ["html", "css"], as_dict=True ) merged_template = "" + template.html final_template = render_template(merged_template, context) From 551936e7c47576fb35f72d90caeb8bd0bffe4094 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Wed, 9 Aug 2023 16:29:36 +0530 Subject: [PATCH 11/15] fix: show logo on certificate only if its present --- lms/lms/print_format/certificate/certificate.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lms/lms/print_format/certificate/certificate.json b/lms/lms/print_format/certificate/certificate.json index cea5f505..0040359b 100644 --- a/lms/lms/print_format/certificate/certificate.json +++ b/lms/lms/print_format/certificate/certificate.json @@ -10,14 +10,14 @@ "doctype": "Print Format", "font_size": 14, "format_data": "{\"header\":\"
\\n\\t

LMS Certificate

\\n\\t

{{ doc.name }}

\\n
\",\"sections\":[{\"label\":\"\",\"columns\":[{\"label\":\"\",\"fields\":[{\"label\":\"Course\",\"fieldname\":\"course\",\"fieldtype\":\"Link\",\"options\":\"LMS Course\"},{\"label\":\"Member\",\"fieldname\":\"member\",\"fieldtype\":\"Link\",\"options\":\"User\"},{\"label\":\"Member Name\",\"fieldname\":\"member_name\",\"fieldtype\":\"Data\"},{\"label\":\"Evaluator\",\"fieldname\":\"evaluator\",\"fieldtype\":\"Data\",\"options\":\"\"}]},{\"label\":\"\",\"fields\":[{\"label\":\"Issue Date\",\"fieldname\":\"issue_date\",\"fieldtype\":\"Date\"},{\"label\":\"Expiry Date\",\"fieldname\":\"expiry_date\",\"fieldtype\":\"Date\"},{\"label\":\"Version\",\"fieldname\":\"version\",\"fieldtype\":\"Select\",\"options\":\"V13\\nV14\"},{\"label\":\"Module Names for Certificate\",\"fieldname\":\"module_names_for_certificate\",\"fieldtype\":\"Data\"}]}],\"has_fields\":true}]}", - "html": "{% set certificate = frappe.db.get_value(\"LMS Certificate\", doc.name, [\"name\", \"member\", \"issue_date\", \"expiry_date\", \"course\"], as_dict=True) %}\n{% set member = frappe.db.get_value(\"User\", doc.member, [\"full_name\"], as_dict=True) %}\n{% set course = frappe.db.get_value(\"LMS Course\", doc.course, [\"title\", \"name\", \"image\"], as_dict=True) %}\n{% set logo = frappe.db.get_single_value(\"Website Settings\", \"banner_image\") %}\n{% set instructors = frappe.get_all(\"Course Instructor\", {\"parent\": doc.course}, pluck=\"instructor\", order_by=\"idx\") %}\n\n\n\n\n\n\n
\n
\n \n \n
\n {{ _(\"This certifies that\") }}\n
\n \n
\n {{ member.full_name }}\n
\n
\n {{ _(\"has successfully completed the course on\") }}\n {{ course.title }} \n on {{ frappe.utils.format_date(certificate.issue_date, \"medium\") }}.\n
\n \n \n \n {% if instructors %}\n \n {% endif %}\n \n {% if certificate.expiry_date %}\n \n \n \n {% endif %}\n \n
\n \n
\n
{{ _(\"Course Instructor\") }}
\n
\n \n
\n
{{ _(\"Expiry Date\") }}
\n
\n
\n
", + "html": "{% set certificate = frappe.db.get_value(\"LMS Certificate\", doc.name, [\"name\", \"member\", \"issue_date\", \"expiry_date\", \"course\"], as_dict=True) %}\n{% set member = frappe.db.get_value(\"User\", doc.member, [\"full_name\"], as_dict=True) %}\n{% set course = frappe.db.get_value(\"LMS Course\", doc.course, [\"title\", \"name\", \"image\"], as_dict=True) %}\n{% set logo = frappe.db.get_single_value(\"Website Settings\", \"banner_image\") %}\n{% set instructors = frappe.get_all(\"Course Instructor\", {\"parent\": doc.course}, pluck=\"instructor\", order_by=\"idx\") %}\n\n\n\n\n\n\n
\n
\n \n {% if logo %}\n \n {% endif %}\n
\n {{ _(\"This certifies that\") }}\n
\n \n
\n {{ member.full_name }}\n
\n
\n {{ _(\"has successfully completed the course on\") }}\n {{ course.title }} \n on {{ frappe.utils.format_date(certificate.issue_date, \"medium\") }}.\n
\n \n \n \n {% if instructors %}\n \n {% endif %}\n \n {% if certificate.expiry_date %}\n \n \n \n {% endif %}\n \n
\n \n
\n
{{ _(\"Course Instructor\") }}
\n
\n \n
\n
{{ _(\"Expiry Date\") }}
\n
\n
\n
", "idx": 0, "line_breaks": 0, "margin_bottom": 0.0, "margin_left": 0.0, "margin_right": 0.0, "margin_top": 0.0, - "modified": "2023-04-17 13:46:38.633751", + "modified": "2023-08-09 16:29:07.233894", "modified_by": "Administrator", "module": "LMS", "name": "Certificate", From 59f08ad4da691e1a1444df04864d1f6e295e3d05 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Wed, 9 Aug 2023 16:57:25 +0530 Subject: [PATCH 12/15] fix: raise error if certificate is not found in any condition --- .../print_format/certificate/certificate.json | 4 +- lms/www/courses/certificate.py | 41 +++++++++++++------ 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/lms/lms/print_format/certificate/certificate.json b/lms/lms/print_format/certificate/certificate.json index 0040359b..acf11b9e 100644 --- a/lms/lms/print_format/certificate/certificate.json +++ b/lms/lms/print_format/certificate/certificate.json @@ -1,7 +1,7 @@ { "absolute_value": 0, "align_labels_right": 0, - "creation": "2023-02-22 21:36:54.560420", + "creation": "2023-08-09 16:46:41.214175", "css": ".outer-border {\n font-family: \"Inter\" sans-serif;\n font-size: 16px;\n border-radius: 0.5rem;\n border: 1px solid #E2E6E9;\n padding: 1rem;\n}\n\n.inner-border {\n border: 10px solid #0089FF;\n border-radius: 8px;\n text-align: center;\n padding: 6rem 4rem;\n background-color: #FFFFFF;\n}\n\n.certificate-logo {\n height: 1.5rem;\n margin-bottom: 4rem;\n}\n\n.certificate-name {\n font-size: 2rem;\n font-weight: 500;\n color: #192734;\n margin-bottom: 0.5rem;\n}\n\n.certificate-footer {\n margin: 4rem auto 0;\n width: 70%;\n text-align: center;\n}\n\n.certificate-footer-item {\n color: #192734;\n}\n\n.cursive-font {\n font-family: cursive;\n font-weight: 600;\n}\n\n.certificate-divider {\n margin: 0.5rem 0;\n}\n\n.certificate-expiry {\n margin-left: 2rem;\n}", "custom_format": 1, "disabled": 0, @@ -17,7 +17,7 @@ "margin_left": 0.0, "margin_right": 0.0, "margin_top": 0.0, - "modified": "2023-08-09 16:29:07.233894", + "modified": "2023-08-09 16:46:41.214175", "modified_by": "Administrator", "module": "LMS", "name": "Certificate", diff --git a/lms/www/courses/certificate.py b/lms/www/courses/certificate.py index 55144da6..dccb9515 100644 --- a/lms/www/courses/certificate.py +++ b/lms/www/courses/certificate.py @@ -1,4 +1,5 @@ import frappe +from frappe import _ from frappe.utils.jinja import render_template from frappe.utils import get_url @@ -30,18 +31,7 @@ def get_context(context): ) context.url = f"{get_url()}/courses/{context.course.name}/{context.doc.name}" - print_format = frappe.db.get_value( - "Property Setter", - { - "doc_type": "LMS Certificate", - "property": "default_print_format", - }, - ["value"], - as_dict=True, - ) - - if not print_format: - print_format = "Certificate" + print_format = get_print_format() template = frappe.db.get_value( "Print Format", print_format.value, ["html", "css"], as_dict=True @@ -54,3 +44,30 @@ def get_context(context): def redirect_to_course_list(): frappe.local.flags.redirect_location = "/courses" raise frappe.Redirect + + +def get_print_format(): + print_format = None + default = frappe.db.get_value( + "Property Setter", + { + "doc_type": "LMS Certificate", + "property": "default_print_format", + }, + "value", + ) + + if frappe.db.exists("Print Format", default): + print_format = default + + if not print_format and frappe.db.exists("Print Format", "Certificate"): + print_format = "Certificate" + + if not print_format: + raise ValueError( + _( + "Default Print Format is not set for Certificate. Please contact the Administrator." + ) + ) + + return print_format From 066e2ddc69932884d056d66a4608b32418c4a92d Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Wed, 9 Aug 2023 17:03:10 +0530 Subject: [PATCH 13/15] fix: print format value --- lms/lms/print_format/certificate/certificate.json | 4 ++-- lms/www/courses/certificate.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lms/lms/print_format/certificate/certificate.json b/lms/lms/print_format/certificate/certificate.json index acf11b9e..47b27f51 100644 --- a/lms/lms/print_format/certificate/certificate.json +++ b/lms/lms/print_format/certificate/certificate.json @@ -1,7 +1,7 @@ { "absolute_value": 0, "align_labels_right": 0, - "creation": "2023-08-09 16:46:41.214175", + "creation": "2023-08-09 17:02:21.430320", "css": ".outer-border {\n font-family: \"Inter\" sans-serif;\n font-size: 16px;\n border-radius: 0.5rem;\n border: 1px solid #E2E6E9;\n padding: 1rem;\n}\n\n.inner-border {\n border: 10px solid #0089FF;\n border-radius: 8px;\n text-align: center;\n padding: 6rem 4rem;\n background-color: #FFFFFF;\n}\n\n.certificate-logo {\n height: 1.5rem;\n margin-bottom: 4rem;\n}\n\n.certificate-name {\n font-size: 2rem;\n font-weight: 500;\n color: #192734;\n margin-bottom: 0.5rem;\n}\n\n.certificate-footer {\n margin: 4rem auto 0;\n width: 70%;\n text-align: center;\n}\n\n.certificate-footer-item {\n color: #192734;\n}\n\n.cursive-font {\n font-family: cursive;\n font-weight: 600;\n}\n\n.certificate-divider {\n margin: 0.5rem 0;\n}\n\n.certificate-expiry {\n margin-left: 2rem;\n}", "custom_format": 1, "disabled": 0, @@ -17,7 +17,7 @@ "margin_left": 0.0, "margin_right": 0.0, "margin_top": 0.0, - "modified": "2023-08-09 16:46:41.214175", + "modified": "2023-08-09 17:02:21.430320", "modified_by": "Administrator", "module": "LMS", "name": "Certificate", diff --git a/lms/www/courses/certificate.py b/lms/www/courses/certificate.py index dccb9515..91e4de53 100644 --- a/lms/www/courses/certificate.py +++ b/lms/www/courses/certificate.py @@ -34,7 +34,7 @@ def get_context(context): print_format = get_print_format() template = frappe.db.get_value( - "Print Format", print_format.value, ["html", "css"], as_dict=True + "Print Format", print_format, ["html", "css"], as_dict=True ) merged_template = "" + template.html final_template = render_template(merged_template, context) From 55296cd9cc316c3d5cf8c80d635b2a371453e5aa Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Thu, 10 Aug 2023 11:51:16 +0530 Subject: [PATCH 14/15] fix: show progress on class schedule --- lms/lms/utils.py | 7 +++++-- lms/www/classes/class.html | 7 +++++++ lms/www/classes/class.py | 28 +++++++++++++++++++--------- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/lms/lms/utils.py b/lms/lms/utils.py index f7ecaa72..d472e6cf 100644 --- a/lms/lms/utils.py +++ b/lms/lms/utils.py @@ -282,10 +282,13 @@ def get_slugified_chapter_title(chapter): return slugify(chapter) -def get_progress(course, lesson): +def get_progress(course, lesson, member=None): + if not member: + member = frappe.session.user + return frappe.db.get_value( "LMS Course Progress", - {"course": course, "owner": frappe.session.user, "lesson": lesson}, + {"course": course, "owner": member, "lesson": lesson}, ["status"], ) diff --git a/lms/www/classes/class.html b/lms/www/classes/class.html index 0239d298..df151536 100644 --- a/lms/www/classes/class.html +++ b/lms/www/classes/class.html @@ -570,6 +570,13 @@ {{ lesson.title }} + + {% if get_membership(lesson.course, current_student.name) %} + {% set lesson_progress = get_progress(lesson.course, lesson.name, current_student.name) %} + + + + {% endif %}
{{ frappe.utils.format_date(lesson.date, "medium") }} diff --git a/lms/www/classes/class.py b/lms/www/classes/class.py index b57aa904..589d6db0 100644 --- a/lms/www/classes/class.py +++ b/lms/www/classes/class.py @@ -221,16 +221,11 @@ def get_scheduled_flow(class_name): ) for lesson in lessons: - lesson.update( - frappe.db.get_value( - "Course Lesson", lesson.lesson, ["title", "body", "course", "chapter"], as_dict=True - ) - ) - lesson.index = get_lesson_index(lesson.lesson) - lesson.url = get_lesson_url(lesson.course, lesson.index) + "?class=" + class_name - lesson.icon = get_lesson_icon(lesson.body) + lesson = get_lesson_details(lesson, class_name) + chapter_exists = [ + chapter for chapter in chapters if chapter.chapter == lesson.chapter + ] - chapter_exists = list(filter(lambda x: x.chapter == lesson.chapter, chapters)) if len(chapter_exists) == 0: chapters.append( frappe._dict( @@ -247,6 +242,21 @@ def get_scheduled_flow(class_name): return chapters +def get_lesson_details(lesson, class_name): + lesson.update( + frappe.db.get_value( + "Course Lesson", + lesson.lesson, + ["name", "title", "body", "course", "chapter"], + as_dict=True, + ) + ) + lesson.index = get_lesson_index(lesson.lesson) + lesson.url = get_lesson_url(lesson.course, lesson.index) + "?class=" + class_name + lesson.icon = get_lesson_icon(lesson.body) + return lesson + + def get_current_student_details(class_courses, class_name): student_details = frappe._dict() student_details.courses = frappe._dict() From 03620be7bb6ac4f1c8257773f6032f90d5347582 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Thu, 10 Aug 2023 13:00:26 +0530 Subject: [PATCH 15/15] fix: class schedule date and time validation --- lms/lms/doctype/lms_class/lms_class.js | 3 --- lms/lms/doctype/lms_class/lms_class.json | 21 +++++++++-------- lms/lms/doctype/lms_class/lms_class.py | 30 ++++++++++++++++++++++++ lms/www/classes/class.html | 2 +- 4 files changed, 42 insertions(+), 14 deletions(-) diff --git a/lms/lms/doctype/lms_class/lms_class.js b/lms/lms/doctype/lms_class/lms_class.js index d8a510ef..7f3c4299 100644 --- a/lms/lms/doctype/lms_class/lms_class.js +++ b/lms/lms/doctype/lms_class/lms_class.js @@ -21,13 +21,10 @@ frappe.ui.form.on("LMS Class", { }, callback: (r) => { if (r.message) { - let date = frappe.datetime.get_today(); r.message.forEach((lesson) => { let row = frm.add_child("scheduled_flow"); row.lesson = lesson.name; row.lesson_title = lesson.title; - row.date = date; - date = frappe.datetime.add_days(date, 1); }); frm.refresh_field("scheduled_flow"); } diff --git a/lms/lms/doctype/lms_class/lms_class.json b/lms/lms/doctype/lms_class/lms_class.json index 39f1ed36..dd789b13 100644 --- a/lms/lms/doctype/lms_class/lms_class.json +++ b/lms/lms/doctype/lms_class/lms_class.json @@ -12,8 +12,8 @@ "start_date", "end_date", "column_break_4", - "end_time", "start_time", + "end_time", "section_break_rgfj", "medium", "category", @@ -24,13 +24,13 @@ "description", "students", "courses", - "section_break_lbwu", - "fetch_lessons", - "scheduled_flow", "section_break_ubxi", "custom_component", "assessment_tab", - "assessment" + "assessment", + "schedule_tab", + "fetch_lessons", + "scheduled_flow" ], "fields": [ { @@ -139,10 +139,6 @@ "fieldtype": "Autocomplete", "label": "Category" }, - { - "fieldname": "section_break_lbwu", - "fieldtype": "Section Break" - }, { "fieldname": "scheduled_flow", "fieldtype": "Table", @@ -157,11 +153,16 @@ "fieldname": "fetch_lessons", "fieldtype": "Button", "label": "Fetch Lessons" + }, + { + "fieldname": "schedule_tab", + "fieldtype": "Tab Break", + "label": "Schedule" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2023-07-31 15:45:03.839896", + "modified": "2023-08-10 12:54:44.351907", "modified_by": "Administrator", "module": "LMS", "name": "LMS Class", diff --git a/lms/lms/doctype/lms_class/lms_class.py b/lms/lms/doctype/lms_class/lms_class.py index 36d7d255..b625d01b 100644 --- a/lms/lms/doctype/lms_class/lms_class.py +++ b/lms/lms/doctype/lms_class/lms_class.py @@ -19,6 +19,7 @@ class LMSClass(Document): self.validate_duplicate_students() self.validate_duplicate_assessments() self.validate_membership() + self.validate_schedule() def validate_duplicate_students(self): students = [row.student for row in self.students] @@ -67,6 +68,35 @@ class LMSClass(Document): if cint(self.seat_count) < len(self.students): frappe.throw(_("There are no seats available in this class.")) + def validate_schedule(self): + for schedule in self.scheduled_flow: + if schedule.start_time and schedule.end_time: + if ( + schedule.start_time > schedule.end_time or schedule.start_time == schedule.end_time + ): + frappe.throw( + _("Row #{0} Start time cannot be greater than or equal to end time.").format( + schedule.idx + ) + ) + + if schedule.start_time < self.start_time or schedule.start_time > self.end_time: + frappe.throw( + _("Row #{0} Start time cannot be outside the class duration.").format( + schedule.idx + ) + ) + + if schedule.end_time < self.start_time or schedule.end_time > self.end_time: + frappe.throw( + _("Row #{0} End time cannot be outside the class duration.").format(schedule.idx) + ) + + if schedule.date < self.start_date or schedule.date > self.end_date: + frappe.throw( + _("Row #{0} Date cannot be outside the class duration.").format(schedule.idx) + ) + @frappe.whitelist() def remove_student(student, class_name): diff --git a/lms/www/classes/class.html b/lms/www/classes/class.html index df151536..8726da4b 100644 --- a/lms/www/classes/class.html +++ b/lms/www/classes/class.html @@ -571,7 +571,7 @@ {{ lesson.title }} - {% if get_membership(lesson.course, current_student.name) %} + {% if current_student.name and get_membership(lesson.course, current_student.name) %} {% set lesson_progress = get_progress(lesson.course, lesson.name, current_student.name) %}