From 11e6b8a3726bff6c16bd06b5609e43fd4d4df412 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Thu, 13 Jul 2023 15:10:53 +0530 Subject: [PATCH 1/8] feat: class evaluators --- lms/install.py | 14 ++++++++++ .../doctype/class_course/class_course.json | 13 +++++++--- .../course_evaluator/course_evaluator.json | 26 ++++++++++++++++++- .../course_evaluator/course_evaluator.py | 1 + .../lms_certificate_evaluation.json | 14 +++++++++- .../lms_certificate_request.json | 14 +++++++++- lms/lms/doctype/lms_class/lms_class.json | 14 +++++++++- lms/patches.txt | 5 ++++ .../v1_0/create_class_evaluator_role.py | 5 ++++ lms/www/classes/class.html | 2 +- lms/www/classes/class.js | 11 +++++++- 11 files changed, 110 insertions(+), 9 deletions(-) create mode 100644 lms/patches/v1_0/create_class_evaluator_role.py diff --git a/lms/install.py b/lms/install.py index 8265d694..fca317b0 100644 --- a/lms/install.py +++ b/lms/install.py @@ -52,6 +52,7 @@ def before_uninstall(): def create_lms_roles(): create_course_creator_role() create_moderator_role() + create_evaluator_role() def delete_lms_roles(): @@ -91,6 +92,19 @@ def create_moderator_role(): role.save(ignore_permissions=True) +def create_evaluator_role(): + if not frappe.db.exists("Role", "Class Evaluator"): + role = frappe.new_doc("Role") + role.update( + { + "role_name": "Class Evaluator", + "home_page": "", + "desk_access": 0, + } + ) + role.save(ignore_permissions=True) + + def delete_custom_fields(): fields = [ "user_category", diff --git a/lms/lms/doctype/class_course/class_course.json b/lms/lms/doctype/class_course/class_course.json index f53619c4..6a614436 100644 --- a/lms/lms/doctype/class_course/class_course.json +++ b/lms/lms/doctype/class_course/class_course.json @@ -7,7 +7,8 @@ "engine": "InnoDB", "field_order": [ "course", - "title" + "title", + "evaluator" ], "fields": [ { @@ -23,14 +24,20 @@ "fieldname": "title", "fieldtype": "Data", "in_list_view": 1, - "label": "Title", + "label": "Course Title", "read_only": 1 + }, + { + "fieldname": "evaluator", + "fieldtype": "Link", + "label": "Evaluator", + "options": "Course Evaluator" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-11-11 15:51:45.560864", + "modified": "2023-07-13 14:21:49.953345", "modified_by": "Administrator", "module": "LMS", "name": "Class Course", diff --git a/lms/lms/doctype/course_evaluator/course_evaluator.json b/lms/lms/doctype/course_evaluator/course_evaluator.json index 07cbcb20..cac4f195 100644 --- a/lms/lms/doctype/course_evaluator/course_evaluator.json +++ b/lms/lms/doctype/course_evaluator/course_evaluator.json @@ -27,7 +27,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2022-04-01 15:14:03.300260", + "modified": "2023-07-13 11:30:22.641076", "modified_by": "Administrator", "module": "LMS", "name": "Course Evaluator", @@ -45,6 +45,30 @@ "role": "System Manager", "share": 1, "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Moderator", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Class Evaluator", + "share": 1, + "write": 1 } ], "sort_field": "modified", diff --git a/lms/lms/doctype/course_evaluator/course_evaluator.py b/lms/lms/doctype/course_evaluator/course_evaluator.py index c868c2f8..de3a6587 100644 --- a/lms/lms/doctype/course_evaluator/course_evaluator.py +++ b/lms/lms/doctype/course_evaluator/course_evaluator.py @@ -42,6 +42,7 @@ def get_schedule(course, date): "Evaluator Schedule", filters={"parent": evaluator}, fields=["day", "start_time", "end_time"], + order_by="start_time", ) booked_slots = frappe.get_all( "LMS Certificate Request", diff --git a/lms/lms/doctype/lms_certificate_evaluation/lms_certificate_evaluation.json b/lms/lms/doctype/lms_certificate_evaluation/lms_certificate_evaluation.json index 87960137..fbc3af60 100644 --- a/lms/lms/doctype/lms_certificate_evaluation/lms_certificate_evaluation.json +++ b/lms/lms/doctype/lms_certificate_evaluation/lms_certificate_evaluation.json @@ -104,7 +104,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2023-02-22 16:00:34.361934", + "modified": "2023-07-13 11:30:53.432076", "modified_by": "Administrator", "module": "LMS", "name": "LMS Certificate Evaluation", @@ -121,6 +121,18 @@ "role": "System Manager", "share": 1, "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Class Evaluator", + "share": 1, + "write": 1 } ], "sort_field": "modified", diff --git a/lms/lms/doctype/lms_certificate_request/lms_certificate_request.json b/lms/lms/doctype/lms_certificate_request/lms_certificate_request.json index 3532d24b..275dd2bd 100644 --- a/lms/lms/doctype/lms_certificate_request/lms_certificate_request.json +++ b/lms/lms/doctype/lms_certificate_request/lms_certificate_request.json @@ -93,7 +93,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2023-02-28 19:53:17.534351", + "modified": "2023-07-13 11:30:41.740461", "modified_by": "Administrator", "module": "LMS", "name": "LMS Certificate Request", @@ -111,6 +111,18 @@ "select": 1, "share": 1, "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Class Evaluator", + "share": 1, + "write": 1 } ], "sort_field": "modified", diff --git a/lms/lms/doctype/lms_class/lms_class.json b/lms/lms/doctype/lms_class/lms_class.json index 3f3b61e3..7253100b 100644 --- a/lms/lms/doctype/lms_class/lms_class.json +++ b/lms/lms/doctype/lms_class/lms_class.json @@ -138,7 +138,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2023-06-22 15:57:25.190084", + "modified": "2023-07-13 11:30:09.097605", "modified_by": "Administrator", "module": "LMS", "name": "LMS Class", @@ -168,6 +168,18 @@ "role": "Moderator", "share": 1, "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Class Evaluator", + "share": 1, + "write": 1 } ], "sort_field": "modified", diff --git a/lms/patches.txt b/lms/patches.txt index 22e92dab..f0d194df 100644 --- a/lms/patches.txt +++ b/lms/patches.txt @@ -56,3 +56,8 @@ lms.patches.v0_0.convert_course_description_to_html #11-05-2023 lms.patches.v1_0.rename_assignment_doctype execute:frappe.permissions.reset_perms("LMS Assignment") execute:frappe.permissions.reset_perms("LMS Quiz") +lms.patches.v1_0.create_class_evaluator_role +execute:frappe.permissions.reset_perms("LMS Class") +execute:frappe.permissions.reset_perms("Course Evaluator") +execute:frappe.permissions.reset_perms("LMS Certificate Request") +execute:frappe.permissions.reset_perms("LMS Certificate Evaluation") \ No newline at end of file diff --git a/lms/patches/v1_0/create_class_evaluator_role.py b/lms/patches/v1_0/create_class_evaluator_role.py new file mode 100644 index 00000000..ffabe2ea --- /dev/null +++ b/lms/patches/v1_0/create_class_evaluator_role.py @@ -0,0 +1,5 @@ +from lms.install import create_evaluator_role + + +def execute(): + create_evaluator_role() diff --git a/lms/www/classes/class.html b/lms/www/classes/class.html index 3193e05a..f566e229 100644 --- a/lms/www/classes/class.html +++ b/lms/www/classes/class.html @@ -180,7 +180,7 @@ {% if class_courses | length %}
{% for course in class_courses %} -
+
{{ widgets.CourseCard(course=course, read_only=False) }}
- {% if is_moderator %} - {% endif %} -
@@ -107,4 +113,18 @@ {% else %}

{{ _("No Assessments") }}

{% endif %} -{% endmacro %} \ No newline at end of file +{% endmacro %} + +{%- block script %} + {{ super() }} + + {{ include_script('controls.bundle.js') }} +{% endblock %} \ No newline at end of file diff --git a/lms/www/classes/progress.js b/lms/www/classes/progress.js index 6c136c9a..5071da22 100644 --- a/lms/www/classes/progress.js +++ b/lms/www/classes/progress.js @@ -2,4 +2,134 @@ 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, + get_query: () => { + return { + filters: { + name: ["in", courses], + }, + }; + }, + }, + { + fieldtype: "Date", + fieldname: "date", + label: __("Date"), + reqd: 1, + min_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) { + console.log(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"), + }, + callback: (r) => { + if (r.message) { + frappe.msgprint({ + title: __("Success"), + message: __("Evaluation scheduled successfully"), + }); + this.eval_form.hide(); + } + }, + }); +}; diff --git a/lms/www/classes/progress.py b/lms/www/classes/progress.py index be922dd1..b8a6e318 100644 --- a/lms/www/classes/progress.py +++ b/lms/www/classes/progress.py @@ -21,4 +21,8 @@ def get_context(context): "LMS Class", class_name, ["name"], as_dict=True ) + context.courses = frappe.get_all( + "Class Course", {"parent": class_name}, pluck="course" + ) + context.assessments = get_assessments(class_name, context.student.name) From 295feccb49b490dc7da80f097a391b5658b1cc5b Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Mon, 17 Jul 2023 14:28:25 +0530 Subject: [PATCH 4/8] fix: check booked slots against both day and time --- lms/lms/doctype/course_evaluator/course_evaluator.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lms/lms/doctype/course_evaluator/course_evaluator.py b/lms/lms/doctype/course_evaluator/course_evaluator.py index c868c2f8..7f151612 100644 --- a/lms/lms/doctype/course_evaluator/course_evaluator.py +++ b/lms/lms/doctype/course_evaluator/course_evaluator.py @@ -46,11 +46,13 @@ def get_schedule(course, date): booked_slots = frappe.get_all( "LMS Certificate Request", filters={"evaluator": evaluator, "date": date}, - fields=["start_time"], + fields=["start_time", "day"], ) for slot in booked_slots: - same_slot = list(filter(lambda x: x.start_time == slot.start_time, all_slots)) + same_slot = list( + filter(lambda x: x.start_time == slot.start_time and x.day == slot.day, all_slots) + ) if len(same_slot): all_slots.remove(same_slot[0]) From 0182db803000fa6394bcdc1454c1e5c4de46ef5b Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Thu, 20 Jul 2023 20:16:13 +0530 Subject: [PATCH 5/8] fix: show upcoming evals in progress page --- .../course_evaluator/course_evaluator.py | 13 +- .../lms_certificate_request.py | 8 +- lms/lms/utils.py | 16 ++ lms/www/classes/progress.html | 143 +++++++++++++----- lms/www/classes/progress.js | 30 ++-- lms/www/classes/progress.py | 17 +++ 6 files changed, 156 insertions(+), 71 deletions(-) diff --git a/lms/lms/doctype/course_evaluator/course_evaluator.py b/lms/lms/doctype/course_evaluator/course_evaluator.py index beff1c6e..b8fc78bd 100644 --- a/lms/lms/doctype/course_evaluator/course_evaluator.py +++ b/lms/lms/doctype/course_evaluator/course_evaluator.py @@ -4,6 +4,7 @@ import frappe from frappe import _ from frappe.model.document import Document +from lms.lms.utils import get_evaluator class CourseEvaluator(Document): @@ -37,17 +38,7 @@ class CourseEvaluator(Document): @frappe.whitelist() def get_schedule(course, date, class_name=None): - evaluator = None - - if class_name: - evaluator = frappe.db.get_value( - "Class Course", - {"parent": class_name, "course": course}, - "evaluator", - ) - - if not evaluator: - evaluator = frappe.db.get_value("LMS Course", course, "evaluator") + evaluator = get_evaluator(course, class_name) all_slots = frappe.get_all( "Evaluator Schedule", diff --git a/lms/lms/doctype/lms_certificate_request/lms_certificate_request.py b/lms/lms/doctype/lms_certificate_request/lms_certificate_request.py index 0570d615..f10da005 100644 --- a/lms/lms/doctype/lms_certificate_request/lms_certificate_request.py +++ b/lms/lms/doctype/lms_certificate_request/lms_certificate_request.py @@ -6,6 +6,7 @@ from frappe import _ from frappe.model.document import Document from frappe.model.mapper import get_mapped_doc from frappe.utils import format_date, format_time, getdate +from lms.lms.utils import get_evaluator class LMSCertificateRequest(Document): @@ -24,7 +25,7 @@ class LMSCertificateRequest(Document): ) for req in existing_requests: - if req.date == getdate(self.date) and getdate() <= getdate(self.date): + if req.date == getdate(self.date) or getdate() <= getdate(self.date): course_title = frappe.db.get_value("LMS Course", req.course, "title") frappe.throw( _("You already have an evaluation on {0} at {1} for the course {2}.").format( @@ -78,7 +79,9 @@ class LMSCertificateRequest(Document): @frappe.whitelist() -def create_certificate_request(course, date, day, start_time, end_time): +def create_certificate_request( + course, date, day, start_time, end_time, class_name=None +): is_member = frappe.db.exists( {"doctype": "LMS Batch Membership", "course": course, "member": frappe.session.user} ) @@ -90,6 +93,7 @@ def create_certificate_request(course, date, day, start_time, end_time): { "doctype": "LMS Certificate Request", "course": course, + "evaluator": get_evaluator(course, class_name), "member": frappe.session.user, "date": date, "day": day, diff --git a/lms/lms/utils.py b/lms/lms/utils.py index 6e2d981d..17619038 100644 --- a/lms/lms/utils.py +++ b/lms/lms/utils.py @@ -753,3 +753,19 @@ def has_submitted_assessment(assessment, type, member=None): def has_graded_assessment(submission): status = frappe.db.get_value("LMS Assignment Submission", submission, "status") return False if status == "Not Graded" else True + + +def get_evaluator(course, class_name=None): + evaluator = None + + if class_name: + evaluator = frappe.db.get_value( + "Class Course", + {"parent": class_name, "course": course}, + "evaluator", + ) + + if not evaluator: + evaluator = frappe.db.get_value("LMS Course", course, "evaluator") + + return evaluator diff --git a/lms/www/classes/progress.html b/lms/www/classes/progress.html index f49e47cd..fc26578a 100644 --- a/lms/www/classes/progress.html +++ b/lms/www/classes/progress.html @@ -21,7 +21,7 @@
{{ _("{0}").format(student.full_name) }}
-
+
{{ _("All Classes") }} @@ -58,61 +58,122 @@ {% macro Progress(class_info, student) %} -{% if assessments | length %} -
-
+ {{ UpcomingEvals(upcoming_evals) }} + {{ Assessments(class_info, student) }} +{% endmacro %} + +{% macro UpcomingEvals(upcoming_evals) %} +
+
+ {{ _("Upcoming Evaluations") }} +
+ {% if upcoming_evals | length %} +
+
+
+
+
+ {{ _("Course") }} +
+
+ {{ _("Date") }} +
+
+ {{ _("Time") }} +
+
+ {{ _("Evaluator") }} +
+
+
+
+ {% for eval in upcoming_evals %}
-
- {{ _("Assessment") }} + + {{ eval.course_title }} + +
+ {{ eval.date }}
- {{ _("Type") }} + {{ eval.start_time }}
- {{ _("Status/Score") }} + {{ eval.evaluator_name }}
-
- {% 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] }} -
+ {% endfor %} +
+ {% else %} +

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

+ {% endif %} +
+{% endmacro %} -
- {% 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 }} -
+{% 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 %} -
- {{ assessment.submission.score }} +
+ {{ _("Not Attempted") }}
{% endif %} - {% else %} -
- {{ _("Not Attempted") }} -
- {% endif %} -
+
+
-
- {% endfor %} -
-{% else %} -

{{ _("No Assessments") }}

-{% endif %} + {% endfor %} + + {% else %} +

{{ _("No Assessments") }}

+ {% endif %} +
+ {% endmacro %} {%- block script %} diff --git a/lms/www/classes/progress.js b/lms/www/classes/progress.js index 5071da22..53f46cb6 100644 --- a/lms/www/classes/progress.js +++ b/lms/www/classes/progress.js @@ -35,9 +35,8 @@ const open_evaluation_form = (e) => { fieldname: "date", label: __("Date"), reqd: 1, - min_date: frappe.datetime.add_days( - frappe.datetime.get_today(), - 1 + min_date: new Date( + frappe.datetime.add_days(frappe.datetime.get_today(), 1) ), change: () => { get_slots(); @@ -69,7 +68,6 @@ const get_slots = () => { }, callback: (r) => { if (r.message) { - console.log(r.message); display_slots(r.message); } }, @@ -82,13 +80,12 @@ const display_slots = (slots) => { 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")} + }" + data-start="${slot.start_time}" data-end="${slot.end_time}"> + ${moment(slot.start_time, "hh:mm").format("hh:mm a")} - + ${moment(slot.end_time, "hh:mm").format("hh:mm a")}
`; } }); @@ -123,13 +120,12 @@ const submit_evaluation_form = (values) => { day: this.current_slot.data("day"), }, callback: (r) => { - if (r.message) { - frappe.msgprint({ - title: __("Success"), - message: __("Evaluation scheduled successfully"), - }); - this.eval_form.hide(); - } + frappe.msgprint({ + title: __("Success"), + message: __("Evaluation scheduled successfully"), + }); + this.eval_form.hide(); + window.location.reload(); }, }); }; diff --git a/lms/www/classes/progress.py b/lms/www/classes/progress.py index b8a6e318..8e5bd323 100644 --- a/lms/www/classes/progress.py +++ b/lms/www/classes/progress.py @@ -26,3 +26,20 @@ 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"], + 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 From 7990675c5c89ea715e9607dd30b3d2b82acf16f3 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Fri, 21 Jul 2023 12:46:33 +0530 Subject: [PATCH 6/8] fix: evaluator in evals and link field descriptions --- .../lms_certificate_request.json | 15 ++++- .../lms_certificate_request.py | 8 +-- lms/lms/utils.py | 9 ++- lms/www/classes/class.html | 2 +- lms/www/classes/class.js | 21 ++----- lms/www/classes/class.py | 2 + lms/www/classes/progress.html | 58 ++++++++----------- lms/www/classes/progress.js | 18 +++--- lms/www/classes/progress.py | 10 +++- 9 files changed, 76 insertions(+), 67 deletions(-) diff --git a/lms/lms/doctype/lms_certificate_request/lms_certificate_request.json b/lms/lms/doctype/lms_certificate_request/lms_certificate_request.json index 275dd2bd..2de888cf 100644 --- a/lms/lms/doctype/lms_certificate_request/lms_certificate_request.json +++ b/lms/lms/doctype/lms_certificate_request/lms_certificate_request.json @@ -38,6 +38,7 @@ }, { "fetch_from": "course.evaluator", + "fetch_if_empty": 1, "fieldname": "evaluator", "fieldtype": "Link", "label": "Evaluator", @@ -93,7 +94,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2023-07-13 11:30:41.740461", + "modified": "2023-07-21 11:13:38.827783", "modified_by": "Administrator", "module": "LMS", "name": "LMS Certificate Request", @@ -123,6 +124,18 @@ "role": "Class Evaluator", "share": 1, "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Moderator", + "share": 1, + "write": 1 } ], "sort_field": "modified", diff --git a/lms/lms/doctype/lms_certificate_request/lms_certificate_request.py b/lms/lms/doctype/lms_certificate_request/lms_certificate_request.py index f10da005..b890cc40 100644 --- a/lms/lms/doctype/lms_certificate_request/lms_certificate_request.py +++ b/lms/lms/doctype/lms_certificate_request/lms_certificate_request.py @@ -88,10 +88,9 @@ def create_certificate_request( if not is_member: return - - frappe.get_doc( + eval = frappe.new_doc("LMS Certificate Request") + eval.update( { - "doctype": "LMS Certificate Request", "course": course, "evaluator": get_evaluator(course, class_name), "member": frappe.session.user, @@ -100,7 +99,8 @@ def create_certificate_request( "start_time": start_time, "end_time": end_time, } - ).save(ignore_permissions=True) + ) + eval.save() @frappe.whitelist() diff --git a/lms/lms/utils.py b/lms/lms/utils.py index 17619038..efece300 100644 --- a/lms/lms/utils.py +++ b/lms/lms/utils.py @@ -1,6 +1,5 @@ import re import string - import frappe from frappe import _ from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_result @@ -522,6 +521,14 @@ 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"}, + "name", + ) + + def get_courses_under_review(): return frappe.get_all( "LMS Course", diff --git a/lms/www/classes/class.html b/lms/www/classes/class.html index f566e229..c81d56a1 100644 --- a/lms/www/classes/class.html +++ b/lms/www/classes/class.html @@ -247,7 +247,7 @@
{% for student in class_students %} - {% set allow_progress = is_moderator or student.student == frappe.session.user %} + {% set allow_progress = is_moderator or student.student == frappe.session.user or is_evaluator %}
diff --git a/lms/www/classes/class.js b/lms/www/classes/class.js index 1830ee43..a8a45a9e 100644 --- a/lms/www/classes/class.js +++ b/lms/www/classes/class.js @@ -301,14 +301,6 @@ const show_course_modal = () => { fieldname: "course", reqd: 1, }, - { - fieldtype: "Link", - options: "Course Evaluator", - label: __("Evaluator"), - fieldname: "evaluator", - fetch_from: "course.evaluator", - reqd: 1, - }, ], primary_action_label: __("Add"), primary_action(values) { @@ -318,7 +310,7 @@ const show_course_modal = () => { }); course_modal.show(); setTimeout(() => { - $(".modal-body").css("min-height", "300px"); + $(".modal-body").css("min-height", "200px"); }, 1000); }; @@ -329,7 +321,6 @@ const add_course = (values) => { doc: { doctype: "Class Course", course: values.course, - evaluator: values.evaluator, parenttype: "LMS Class", parentfield: "courses", parent: $(".class-details").data("class"), @@ -383,6 +374,7 @@ const show_student_modal = () => { filters: { ignore_user_type: 1, }, + filter_description: " ", }, ], primary_action_label: __("Add"), @@ -457,13 +449,10 @@ const show_assessment_modal = (e) => { label: __("Assessment Type"), fieldname: "assessment_type", reqd: 1, - get_query: () => { - return { - filters: { - name: ["in", ["LMS Assignment", "LMS Quiz"]], - }, - }; + filters: { + name: ["in", ["LMS Assignment", "LMS Quiz"]], }, + filter_description: " ", }, { fieldtype: "Dynamic Link", diff --git a/lms/www/classes/class.py b/lms/www/classes/class.py index 59932854..74b4b5a3 100644 --- a/lms/www/classes/class.py +++ b/lms/www/classes/class.py @@ -4,6 +4,7 @@ from frappe.utils import getdate from lms.www.utils import get_assessments from lms.lms.utils import ( has_course_moderator_role, + has_course_evaluator_role, get_course_progress, has_submitted_assessment, has_graded_assessment, @@ -14,6 +15,7 @@ def get_context(context): context.no_cache = 1 class_name = frappe.form_dict["classname"] context.is_moderator = has_course_moderator_role() + context.is_evaluator = has_course_evaluator_role() context.class_info = frappe.db.get_value( "LMS Class", diff --git a/lms/www/classes/progress.html b/lms/www/classes/progress.html index fc26578a..94bc597c 100644 --- a/lms/www/classes/progress.html +++ b/lms/www/classes/progress.html @@ -68,46 +68,36 @@ {{ _("Upcoming Evaluations") }}
{% if upcoming_evals | length %} -
-
-
-
-
- {{ _("Course") }} -
-
- {{ _("Date") }} -
-
- {{ _("Time") }} -
-
- {{ _("Evaluator") }} -
-
-
-
+
{% for eval in upcoming_evals %} -
-
- - {{ eval.course_title }} - -
- {{ eval.date }} -
-
- {{ eval.start_time }} -
-
+
+
+ {{ eval.course_title }} +
+
+ + + + + {{ 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") }}

+

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

{% endif %}
{% endmacro %} @@ -170,7 +160,7 @@ {% endfor %} {% else %} -

{{ _("No Assessments") }}

+

{{ _("No Assessments") }}

{% endif %} diff --git a/lms/www/classes/progress.js b/lms/www/classes/progress.js index 53f46cb6..25bb3047 100644 --- a/lms/www/classes/progress.js +++ b/lms/www/classes/progress.js @@ -22,13 +22,10 @@ const open_evaluation_form = (e) => { label: __("Course"), options: "LMS Course", reqd: 1, - get_query: () => { - return { - filters: { - name: ["in", courses], - }, - }; + filters: { + name: ["in", courses], }, + filter_description: " ", }, { fieldtype: "Date", @@ -118,14 +115,17 @@ const submit_evaluation_form = (values) => { 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.msgprint({ - title: __("Success"), + frappe.show_alert({ message: __("Evaluation scheduled successfully"), + indicator: "green", }); this.eval_form.hide(); - window.location.reload(); + setTimeout(() => { + window.location.reload(); + }, 1000); }, }); }; diff --git a/lms/www/classes/progress.py b/lms/www/classes/progress.py index 8e5bd323..5803ed42 100644 --- a/lms/www/classes/progress.py +++ b/lms/www/classes/progress.py @@ -1,5 +1,5 @@ import frappe -from lms.lms.utils import has_course_moderator_role +from lms.lms.utils import has_course_moderator_role, has_course_evaluator_role from frappe import _ from lms.www.utils import get_assessments @@ -10,6 +10,7 @@ def get_context(context): student = frappe.form_dict["username"] class_name = frappe.form_dict["classname"] context.is_moderator = has_course_moderator_role() + context.is_evaluator = has_course_evaluator_role() context.student = frappe.db.get_value( "User", @@ -17,6 +18,13 @@ def get_context(context): ["first_name", "full_name", "name", "last_active", "username"], as_dict=True, ) + if ( + not context.is_moderator + and not context.is_evaluator + and not context.student.name == frappe.session.user + ): + raise frappe.PermissionError(_("You don't have permission to access this page.")) + context.class_info = frappe.db.get_value( "LMS Class", class_name, ["name"], as_dict=True ) From bc8827547e4f883ef8386283ff7f000ce301058c Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Mon, 24 Jul 2023 17:10:03 +0530 Subject: [PATCH 7/8] fix: scheduled the eval event creation --- lms/hooks.py | 10 +- .../lms_certificate_request.json | 9 +- .../lms_certificate_request.py | 108 +++++++++++------- lms/www/classes/progress.html | 12 +- lms/www/classes/progress.js | 2 +- lms/www/classes/progress.py | 2 +- 6 files changed, 91 insertions(+), 52 deletions(-) diff --git a/lms/hooks.py b/lms/hooks.py index b0e04048..1bb0f1f0 100644 --- a/lms/hooks.py +++ b/lms/hooks.py @@ -103,11 +103,11 @@ doc_events = { # Scheduled Tasks # --------------- -# scheduler_events = { -# "daily": [ -# "erpnext.stock.reorder_item.reorder_item" -# ] -# } +scheduler_events = { + "hourly": [ + "lms.lms.doctype.lms_certificate_request.lms_certificate_request.schedule_evals" + ] +} fixtures = ["Custom Field", "Function", "Industry"] diff --git a/lms/lms/doctype/lms_certificate_request/lms_certificate_request.json b/lms/lms/doctype/lms_certificate_request/lms_certificate_request.json index 2de888cf..deb300ce 100644 --- a/lms/lms/doctype/lms_certificate_request/lms_certificate_request.json +++ b/lms/lms/doctype/lms_certificate_request/lms_certificate_request.json @@ -14,6 +14,7 @@ "section_break_lifi", "date", "day", + "google_meet_link", "column_break_ddyh", "start_time", "end_time" @@ -90,11 +91,17 @@ { "fieldname": "column_break_ddyh", "fieldtype": "Column Break" + }, + { + "fieldname": "google_meet_link", + "fieldtype": "Data", + "label": "Google Meet Link", + "read_only": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2023-07-21 11:13:38.827783", + "modified": "2023-07-21 16:00:11.795521", "modified_by": "Administrator", "module": "LMS", "name": "LMS Certificate Request", diff --git a/lms/lms/doctype/lms_certificate_request/lms_certificate_request.py b/lms/lms/doctype/lms_certificate_request/lms_certificate_request.py index b890cc40..475643f1 100644 --- a/lms/lms/doctype/lms_certificate_request/lms_certificate_request.py +++ b/lms/lms/doctype/lms_certificate_request/lms_certificate_request.py @@ -5,7 +5,7 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.model.mapper import get_mapped_doc -from frappe.utils import format_date, format_time, getdate +from frappe.utils import format_date, format_time, getdate, add_to_date, get_datetime from lms.lms.utils import get_evaluator @@ -13,14 +13,10 @@ class LMSCertificateRequest(Document): def validate(self): self.validate_if_existing_requests() - def after_insert(self): - if frappe.db.get_single_value("LMS Settings", "send_calendar_invite_for_evaluations"): - self.create_event() - def validate_if_existing_requests(self): existing_requests = frappe.get_all( "LMS Certificate Request", - {"member": self.member, "course": self.course}, + {"member": self.member, "course": self.course, "name": ["!=", self.name]}, ["date", "start_time", "course"], ) @@ -35,47 +31,75 @@ class LMSCertificateRequest(Document): ) ) - def create_event(self): - calendar = frappe.db.get_value( - "Google Calendar", {"user": self.evaluator, "enable": 1}, "name" + +def schedule_evals(): + if frappe.db.get_single_value("LMS Settings", "send_calendar_invite_for_evaluations"): + one_hour_ago = add_to_date(get_datetime(), hours=-1) + evals = frappe.get_all( + "LMS Certificate Request", + {"creation": [">=", one_hour_ago], "google_meet_link": ["is", "not set"]}, + ["name", "member", "member_name", "evaluator", "date", "start_time", "end_time"], ) + for eval in evals: + setup_calendar_event(eval) - if calendar: - event = frappe.get_doc( - { - "doctype": "Event", - "subject": f"Evaluation of {self.member_name}", - "starts_on": f"{self.date} {self.start_time}", - "ends_on": f"{self.date} {self.end_time}", - } - ) - event.save() - participants = [self.member, self.evaluator] - for participant in participants: - contact_name = frappe.db.get_value("Contact", {"email_id": participant}, "name") - frappe.get_doc( - { - "doctype": "Event Participants", - "reference_doctype": "Contact", - "reference_docname": contact_name, - "email": participant, - "parent": event.name, - "parenttype": "Event", - "parentfield": "event_participants", - } - ).save() +def setup_calendar_event(eval): + calendar = frappe.db.get_value( + "Google Calendar", {"user": eval.evaluator, "enable": 1}, "name" + ) - event.reload() - event.update( - { - "sync_with_google_calendar": 1, - "add_video_conferencing": 1, - "google_calendar": calendar, - } - ) + if calendar: + event = create_event(eval) + add_participants(eval, event) + update_meeting_details(eval, event, calendar) - event.save() + +def create_event(eval): + event = frappe.get_doc( + { + "doctype": "Event", + "subject": f"Evaluation of {eval.member_name}", + "starts_on": f"{eval.date} {eval.start_time}", + "ends_on": f"{eval.date} {eval.end_time}", + } + ) + event.save() + return event + + +def add_participants(eval, event): + participants = [eval.member, eval.evaluator] + for participant in participants: + contact_name = frappe.db.get_value("Contact", {"email_id": participant}, "name") + frappe.get_doc( + { + "doctype": "Event Participants", + "reference_doctype": "Contact", + "reference_docname": contact_name, + "email": participant, + "parent": event.name, + "parenttype": "Event", + "parentfield": "event_participants", + } + ).save() + + +def update_meeting_details(eval, event, calendar): + event.reload() + event.update( + { + "sync_with_google_calendar": 1, + "add_video_conferencing": 1, + "google_calendar": calendar, + } + ) + + event.save() + event.reload() + frappe.db.set_value( + "LMS Certificate Request", eval.name, "google_meet_link", event.google_meet_link + ) @frappe.whitelist() diff --git a/lms/www/classes/progress.html b/lms/www/classes/progress.html index 94bc597c..2639eda7 100644 --- a/lms/www/classes/progress.html +++ b/lms/www/classes/progress.html @@ -71,9 +71,17 @@
{% for eval in upcoming_evals %}
-
- {{ eval.course_title }} +
+
+ {{ eval.course_title }} +
+ {% if eval.google_meet_link %} + + {{ _("Join") }} + + {% endif %}
+
diff --git a/lms/www/classes/progress.js b/lms/www/classes/progress.js index 25bb3047..44c4268b 100644 --- a/lms/www/classes/progress.js +++ b/lms/www/classes/progress.js @@ -107,6 +107,7 @@ const submit_evaluation_form = (values) => { 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: { @@ -122,7 +123,6 @@ const submit_evaluation_form = (values) => { message: __("Evaluation scheduled successfully"), indicator: "green", }); - this.eval_form.hide(); setTimeout(() => { window.location.reload(); }, 1000); diff --git a/lms/www/classes/progress.py b/lms/www/classes/progress.py index 5803ed42..eef9cbee 100644 --- a/lms/www/classes/progress.py +++ b/lms/www/classes/progress.py @@ -42,7 +42,7 @@ def get_context(context): "course": ["in", context.courses], "date": [">=", frappe.utils.nowdate()], }, - ["date", "start_time", "course", "evaluator"], + ["date", "start_time", "course", "evaluator", "google_meet_link"], order_by="date", ) From 294832834c90e309743e767bf4fc8e06cc704911 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Mon, 24 Jul 2023 18:25:36 +0530 Subject: [PATCH 8/8] fix: ignore permissions while eval creations --- .../doctype/lms_certificate_request/lms_certificate_request.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/lms/doctype/lms_certificate_request/lms_certificate_request.py b/lms/lms/doctype/lms_certificate_request/lms_certificate_request.py index 475643f1..82272c1b 100644 --- a/lms/lms/doctype/lms_certificate_request/lms_certificate_request.py +++ b/lms/lms/doctype/lms_certificate_request/lms_certificate_request.py @@ -124,7 +124,7 @@ def create_certificate_request( "end_time": end_time, } ) - eval.save() + eval.save(ignore_permissions=True) @frappe.whitelist()