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/lms/utils.py b/lms/lms/utils.py index db62cdcd..3a2b8b1f 100644 --- a/lms/lms/utils.py +++ b/lms/lms/utils.py @@ -57,21 +57,28 @@ def generate_slug(title, doctype): return slugify(title, used_slugs=slugs) -def get_membership(course, member, batch=None): +def get_membership(course, member=None, batch=None): + if not member: + member = frappe.session.user + filters = {"member": member, "course": course} if batch: filters["batch"] = batch - membership = frappe.db.get_value( - "LMS Batch Membership", - filters, - ["name", "batch", "current_lesson", "member_type", "progress"], - as_dict=True, - ) + is_member = frappe.db.exists("LMS Batch Membership", filters) + if is_member: + membership = frappe.db.get_value( + "LMS Batch Membership", + filters, + ["name", "batch", "current_lesson", "member_type", "progress"], + as_dict=True, + ) - if membership and membership.batch: - membership.batch_title = frappe.db.get_value("LMS Batch", membership.batch, "title") - return membership + if membership and membership.batch: + membership.batch_title = frappe.db.get_value("LMS Batch", membership.batch, "title") + return membership + + return False def get_chapters(course): @@ -524,7 +531,7 @@ def has_course_moderator_role(member=None): def has_course_evaluator_role(member=None): return frappe.db.get_value( "Has Role", - {"parent": member or frappe.session.user, "role": "Evaluator"}, + {"parent": member or frappe.session.user, "role": "Class Evaluator"}, "name", ) @@ -700,7 +707,7 @@ def get_chart_data(chart_name, timespan, timegrain, from_date, to_date): } -@frappe.whitelist(allow_guest=True) +@frappe.whitelist() def get_course_completion_data(): all_membership = frappe.db.count("LMS Batch Membership") completed = frappe.db.count("LMS Batch Membership", {"progress": ["like", "%100%"]}) @@ -785,3 +792,21 @@ def get_evaluator(course, class_name=None): evaluator = frappe.db.get_value("LMS Course", course, "evaluator") return evaluator + + +def get_upcoming_evals(student, courses): + upcoming_evals = frappe.get_all( + "LMS Certificate Request", + { + "member": student, + "course": ["in", 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") + return upcoming_evals diff --git a/lms/public/css/style.css b/lms/public/css/style.css index 7037a6f6..57508a17 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(--shadow-inset); } .course-card { @@ -818,7 +819,7 @@ input[type=checkbox] { } .lesson-pagination { - margin: 2rem 0; + margin: 2rem 0 5rem; } .lesson-video { @@ -1649,10 +1650,6 @@ li { color: var(--gray-600); } -.discussions-parent .empty-state { - background-color: var(--gray-200); -} - .job-cards-parent { display: grid; grid-template-columns: repeat(auto-fill, minmax(500px, 1fr)); @@ -2203,4 +2200,77 @@ select { .rows .grid-row .grid-footer-toolbar, .grid-form-heading { cursor: none; +} + +.lms-page-style .discussions-section-title { + font-size: var(--text-lg); +} + +.class-dashboard .progress { + width: 150px; + height: 150px; + background: none; + position: relative; +} + +.class-dashboard .progress::after { + content: ""; + width: 100%; + height: 100%; + border-radius: 50%; + border: 6px solid #eee; + position: absolute; + top: 0; + left: 0; +} + +.class-dashboard .progress>span { + width: 50%; + height: 100%; + overflow: hidden; + position: absolute; + top: 0; + z-index: 1; +} + +.class-dashboard .progress .progress-left { + left: 0; +} + +.class-dashboard .progress .progress-bar { + width: 100%; + height: 100%; + background: none; + border-width: 6px; + border-style: solid; + position: absolute; + top: 0; +} + +.class-dashboard .progress .progress-left .progress-bar { + left: 100%; + border-top-right-radius: 80px; + border-bottom-right-radius: 80px; + border-left: 0; + -webkit-transform-origin: center left; + transform-origin: center left; +} + +.class-dashboard .progress .progress-right { + right: 0; +} + +.class-dashboard .progress .progress-right .progress-bar { + left: -100%; + border-top-left-radius: 80px; + border-bottom-left-radius: 80px; + border-right: 0; + -webkit-transform-origin: center right; + transform-origin: center right; +} + +.class-dashboard .progress .progress-value { + position: absolute; + top: 0; + left: 0; } \ No newline at end of file diff --git a/lms/public/js/website.bundle.js b/lms/public/js/website.bundle.js index f24ab539..1e86a794 100644 --- a/lms/public/js/website.bundle.js +++ b/lms/public/js/website.bundle.js @@ -1,4 +1,5 @@ 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"; import "../../../../frappe/frappe/public/js/frappe/event_emitter.js"; diff --git a/lms/templates/assessments.html b/lms/templates/assessments.html new file mode 100644 index 00000000..e5f1f0b0 --- /dev/null +++ b/lms/templates/assessments.html @@ -0,0 +1,60 @@ +
+
+ {{ _("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 %} +
\ No newline at end of file diff --git a/lms/templates/upcoming_evals.html b/lms/templates/upcoming_evals.html new file mode 100644 index 00000000..87dc783f --- /dev/null +++ b/lms/templates/upcoming_evals.html @@ -0,0 +1,45 @@ +
+
+ {{ _("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 %} +
\ No newline at end of file diff --git a/lms/www/batch/learn.html b/lms/www/batch/learn.html index 23003702..9c582d68 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") }} +
@@ -236,13 +238,15 @@ {% 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 %} - {%- 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..7b43379e 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")); + } + + 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) => { + this.eval_form.hide(); + 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 74b4b5a3..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, ) @@ -36,14 +37,13 @@ def get_context(context): as_dict=True, ) - context.published_courses = frappe.get_all( - "LMS Course", {"published": 1}, ["name", "title"] - ) + context.reference_doctype = "LMS Class" + context.reference_name = class_name class_courses = frappe.get_all( "Class Course", {"parent": class_name}, - ["name", "course"], + ["name", "course", "title"], order_by="creation desc", ) @@ -62,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 ) @@ -71,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) @@ -136,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 = [] @@ -185,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 d8c2b6e4..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")); - } - - 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) => { - this.eval_form.hide(); - 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)