From bb39999b84459033c6781a3347d963a30621191f Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Tue, 30 May 2023 22:11:14 +0530 Subject: [PATCH] feat: assessment tab in class --- .vscode/settings.json | 3 + lms/hooks.py | 8 + lms/lms/doctype/lms_assessment/__init__.py | 0 .../lms_assessment/lms_assessment.json | 40 +++++ .../doctype/lms_assessment/lms_assessment.py | 9 ++ lms/lms/doctype/lms_assignment/__init__.py | 0 .../doctype/lms_assignment/lms_assignment.js | 8 + .../lms_assignment/lms_assignment.json | 72 +++++++++ .../doctype/lms_assignment/lms_assignment.py | 26 +++ .../lms_assignment/test_lms_assignment.py | 9 ++ .../lms_assignment_submission.json | 75 +++++++-- .../lms_assignment_submission.py | 23 ++- .../test_lms_assignment_submission.py | 2 +- lms/lms/doctype/lms_class/lms_class.json | 17 +- lms/lms/doctype/lms_class/lms_class.py | 19 +++ lms/lms/doctype/lms_course/lms_course.py | 5 +- lms/patches.txt | 1 - .../v0_0/add_evaluator_to_assignment.py | 11 +- lms/public/css/style.css | 47 ++++-- lms/www/assignment_grading/__init__.py | 0 .../assignment_grading.html | 80 +++++++++ .../assignment_grading/assignment_grading.js | 55 +++++++ .../assignment_grading/assignment_grading.py | 35 ++++ lms/www/assignment_submission/__init__.py | 0 .../assignment_submission.html | 72 +++++++++ .../assignment_submission.js | 74 +++++++++ .../assignment_submission.py | 24 +++ lms/www/assignments/assignment.html | 153 ++++++++++-------- lms/www/assignments/assignment.js | 58 +++---- lms/www/assignments/assignment.py | 36 ++--- lms/www/assignments/index.html | 64 ++++++++ lms/www/assignments/index.py | 10 ++ lms/www/batch/edit.js | 2 +- lms/www/batch/quiz.py | 1 - lms/www/classes/class.html | 134 ++++++++++++++- lms/www/classes/class.js | 27 ++++ lms/www/classes/class.py | 34 +++- lms/www/classes/progress.html | 105 +++++------- lms/www/classes/progress.py | 32 +--- lms/www/courses/create.html | 2 +- lms/www/utils.py | 47 ++++++ 41 files changed, 1157 insertions(+), 263 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 lms/lms/doctype/lms_assessment/__init__.py create mode 100644 lms/lms/doctype/lms_assessment/lms_assessment.json create mode 100644 lms/lms/doctype/lms_assessment/lms_assessment.py create mode 100644 lms/lms/doctype/lms_assignment/__init__.py create mode 100644 lms/lms/doctype/lms_assignment/lms_assignment.js create mode 100644 lms/lms/doctype/lms_assignment/lms_assignment.json create mode 100644 lms/lms/doctype/lms_assignment/lms_assignment.py create mode 100644 lms/lms/doctype/lms_assignment/test_lms_assignment.py create mode 100644 lms/www/assignment_grading/__init__.py create mode 100644 lms/www/assignment_grading/assignment_grading.html create mode 100644 lms/www/assignment_grading/assignment_grading.js create mode 100644 lms/www/assignment_grading/assignment_grading.py create mode 100644 lms/www/assignment_submission/__init__.py create mode 100644 lms/www/assignment_submission/assignment_submission.html create mode 100644 lms/www/assignment_submission/assignment_submission.js create mode 100644 lms/www/assignment_submission/assignment_submission.py create mode 100644 lms/www/assignments/index.html create mode 100644 lms/www/assignments/index.py diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..de288e1e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.formatting.provider": "black" +} \ No newline at end of file diff --git a/lms/hooks.py b/lms/hooks.py index d838d31e..04482be2 100644 --- a/lms/hooks.py +++ b/lms/hooks.py @@ -179,7 +179,15 @@ website_route_rules = [ "from_route": "/classes//students/", "to_route": "/classes/progress", }, + { + "from_route": "/assignment-grading/", + "to_route": "assignment_grading/assignment_grading", + }, {"from_route": "/assignments/", "to_route": "assignments/assignment"}, + { + "from_route": "/assignment-submission//", + "to_route": "assignment_submission/assignment_submission", + }, ] website_redirects = [ diff --git a/lms/lms/doctype/lms_assessment/__init__.py b/lms/lms/doctype/lms_assessment/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lms/lms/doctype/lms_assessment/lms_assessment.json b/lms/lms/doctype/lms_assessment/lms_assessment.json new file mode 100644 index 00000000..cab085f9 --- /dev/null +++ b/lms/lms/doctype/lms_assessment/lms_assessment.json @@ -0,0 +1,40 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2023-05-29 14:50:07.910319", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "assessment_type", + "assessment_name" + ], + "fields": [ + { + "fieldname": "assessment_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Assessment Type", + "options": "DocType" + }, + { + "fieldname": "assessment_name", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Assessment Name", + "options": "assessment_type" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2023-05-29 14:56:36.602399", + "modified_by": "Administrator", + "module": "LMS", + "name": "LMS Assessment", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/lms/lms/doctype/lms_assessment/lms_assessment.py b/lms/lms/doctype/lms_assessment/lms_assessment.py new file mode 100644 index 00000000..755e83b2 --- /dev/null +++ b/lms/lms/doctype/lms_assessment/lms_assessment.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 LMSAssessment(Document): + pass diff --git a/lms/lms/doctype/lms_assignment/__init__.py b/lms/lms/doctype/lms_assignment/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lms/lms/doctype/lms_assignment/lms_assignment.js b/lms/lms/doctype/lms_assignment/lms_assignment.js new file mode 100644 index 00000000..14ca7e31 --- /dev/null +++ b/lms/lms/doctype/lms_assignment/lms_assignment.js @@ -0,0 +1,8 @@ +// Copyright (c) 2023, Frappe and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("LMS Assignment", { +// refresh(frm) { + +// }, +// }); diff --git a/lms/lms/doctype/lms_assignment/lms_assignment.json b/lms/lms/doctype/lms_assignment/lms_assignment.json new file mode 100644 index 00000000..e7659c04 --- /dev/null +++ b/lms/lms/doctype/lms_assignment/lms_assignment.json @@ -0,0 +1,72 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "format: ASG-{#####}", + "creation": "2023-05-26 19:41:26.025081", + "default_view": "List", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "column_break_hmwv", + "type", + "section_break_lwvt", + "question" + ], + "fields": [ + { + "fieldname": "question", + "fieldtype": "Text Editor", + "label": "Question" + }, + { + "fieldname": "type", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Type", + "options": "Document\nPDF\nURL\nImage" + }, + { + "fieldname": "title", + "fieldtype": "Data", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Title" + }, + { + "fieldname": "column_break_hmwv", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_lwvt", + "fieldtype": "Section Break" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2023-05-29 14:50:55.259990", + "modified_by": "Administrator", + "module": "LMS", + "name": "LMS Assignment", + "naming_rule": "Expression (old style)", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/lms/lms/doctype/lms_assignment/lms_assignment.py b/lms/lms/doctype/lms_assignment/lms_assignment.py new file mode 100644 index 00000000..4fd23d1d --- /dev/null +++ b/lms/lms/doctype/lms_assignment/lms_assignment.py @@ -0,0 +1,26 @@ +# Copyright (c) 2023, Frappe and contributors +# For license information, please see license.txt + +import frappe +from frappe.model.document import Document +from lms.lms.utils import can_create_courses + + +class LMSAssignment(Document): + pass + + +@frappe.whitelist() +def save_assignment(assignment, title, type, question): + if not can_create_courses(): + return + + if assignment: + doc = frappe.get_doc("LMS Assignment", assignment) + else: + doc = frappe.get_doc({"doctype": "LMS Assignment"}) + + doc.update({"title": title, "type": type, "question": question}) + + doc.save(ignore_permissions=True) + return doc.name diff --git a/lms/lms/doctype/lms_assignment/test_lms_assignment.py b/lms/lms/doctype/lms_assignment/test_lms_assignment.py new file mode 100644 index 00000000..e230054d --- /dev/null +++ b/lms/lms/doctype/lms_assignment/test_lms_assignment.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestLMSAssignment(FrappeTestCase): + pass diff --git a/lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.json b/lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.json index 4a991c2a..8fc10284 100644 --- a/lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.json +++ b/lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.json @@ -1,20 +1,29 @@ { "actions": [], "allow_rename": 1, + "autoname": "format: ASG-SUB-{#####}", "creation": "2021-12-21 16:15:22.651658", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ "assignment", - "lesson", - "course", - "evaluator", - "status", + "assignment_title", + "question", "column_break_3", "member", "member_name", - "comments" + "type", + "assignment_attachment", + "section_break_rqal", + "status", + "column_break_esgd", + "comments", + "section_break_cwaw", + "lesson", + "course", + "column_break_ygdu", + "evaluator" ], "fields": [ { @@ -23,8 +32,7 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Lesson", - "options": "Course Lesson", - "reqd": 1 + "options": "Course Lesson" }, { "fieldname": "column_break_3", @@ -32,8 +40,9 @@ }, { "fieldname": "assignment", - "fieldtype": "Attach", + "fieldtype": "Link", "label": "Assignment", + "options": "LMS Assignment", "reqd": 1 }, { @@ -77,21 +86,65 @@ "label": "Comments" }, { - "fetch_from": "course.evaluator", + "fetch_from": "course.", "fieldname": "evaluator", "fieldtype": "Link", "label": "Evaluator", "options": "User", "read_only": 1 + }, + { + "fieldname": "assignment_attachment", + "fieldtype": "Attach", + "label": "Assignment Attachment", + "reqd": 1 + }, + { + "fetch_from": "assignment.type", + "fieldname": "type", + "fieldtype": "Select", + "label": "Type", + "options": "Document\nPDF\nURL\nImage", + "read_only": 1 + }, + { + "fetch_from": "assignment.question", + "fieldname": "question", + "fieldtype": "Text Editor", + "label": "Question", + "read_only": 1 + }, + { + "fetch_from": "assignment.title", + "fieldname": "assignment_title", + "fieldtype": "Data", + "label": "Assignment Title" + }, + { + "fieldname": "section_break_rqal", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_esgd", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_cwaw", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_ygdu", + "fieldtype": "Column Break" } ], "index_web_pages_for_search": 1, "links": [], "make_attachments_public": 1, - "modified": "2023-03-27 13:24:18.696869", + "modified": "2023-05-30 16:10:09.173258", "modified_by": "Administrator", "module": "LMS", "name": "LMS Assignment Submission", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { @@ -123,5 +176,5 @@ "title": "Fail" } ], - "title_field": "lesson" + "title_field": "assignment_title" } \ No newline at end of file diff --git a/lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.py b/lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.py index 6e98a1f9..92360694 100644 --- a/lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.py +++ b/lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.py @@ -6,14 +6,14 @@ from frappe import _ from frappe.model.document import Document -class LessonAssignment(Document): +class LMSAssignmentSubmission(Document): def validate(self): self.validate_duplicates() def validate_duplicates(self): if frappe.db.exists( "LMS Assignment Submission", - {"lesson": self.lesson, "member": self.member, "name": ["!=", self.name]}, + {"assignment": self.assignment, "member": self.member, "name": ["!=", self.name]}, ): lesson_title = frappe.db.get_value("Course Lesson", self.lesson, "title") frappe.throw( @@ -24,19 +24,26 @@ class LessonAssignment(Document): @frappe.whitelist() -def upload_assignment(assignment, lesson): +def upload_assignment(assignment_attachment, assignment, lesson=None): + if frappe.session.user == "Guest": + return + args = { "doctype": "LMS Assignment Submission", - "lesson": lesson, "member": frappe.session.user, + "assignment": assignment, } if frappe.db.exists(args): del args["doctype"] - frappe.db.set_value("LMS Assignment Submission", args, "assignment", assignment) + frappe.db.set_value( + "LMS Assignment Submission", args, "assignment_attachment", assignment_attachment + ) + return frappe.db.get_value("LMS Assignment Submission", args, "name") else: - args.update({"assignment": assignment}) - lesson_work = frappe.get_doc(args) - lesson_work.save(ignore_permissions=True) + args.update({"assignment_attachment": assignment_attachment}) + doc = frappe.get_doc(args) + doc.save(ignore_permissions=True) + return doc.name @frappe.whitelist() diff --git a/lms/lms/doctype/lms_assignment_submission/test_lms_assignment_submission.py b/lms/lms/doctype/lms_assignment_submission/test_lms_assignment_submission.py index b8792556..27478506 100644 --- a/lms/lms/doctype/lms_assignment_submission/test_lms_assignment_submission.py +++ b/lms/lms/doctype/lms_assignment_submission/test_lms_assignment_submission.py @@ -5,5 +5,5 @@ import unittest -class TestLessonAssignment(unittest.TestCase): +class TestLMSAssignmentSubmission(unittest.TestCase): pass diff --git a/lms/lms/doctype/lms_class/lms_class.json b/lms/lms/doctype/lms_class/lms_class.json index fd0edc2e..a1c69f44 100644 --- a/lms/lms/doctype/lms_class/lms_class.json +++ b/lms/lms/doctype/lms_class/lms_class.json @@ -20,7 +20,9 @@ "description", "students", "courses", - "custom_component" + "custom_component", + "assessment_tab", + "assessment" ], "fields": [ { @@ -96,11 +98,22 @@ "fieldname": "end_time", "fieldtype": "Time", "label": "End Time" + }, + { + "fieldname": "assessment_tab", + "fieldtype": "Tab Break", + "label": "Assessment" + }, + { + "fieldname": "assessment", + "fieldtype": "Table", + "label": "Assessment", + "options": "LMS Assessment" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2023-05-03 23:07:06.725720", + "modified": "2023-05-29 14:52:45.866901", "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 60726434..5612fa45 100644 --- a/lms/lms/doctype/lms_class/lms_class.py +++ b/lms/lms/doctype/lms_class/lms_class.py @@ -180,3 +180,22 @@ def create_class( ) class_details.save() return class_details + + +@frappe.whitelist() +def update_assessment(type, name, value, class_name): + value = cint(value) + filters = { + "assessment_type": type, + "assessment_name": name, + "parent": class_name, + "parenttype": "LMS Class", + "parentfield": "assessment", + } + exists = frappe.db.exists("LMS Assessment", filters) + + if exists and not value: + frappe.db.delete("LMS Assessment", exists) + elif not exists and value: + filters.update({"doctype": "LMS Assessment"}) + frappe.get_doc(filters).insert() diff --git a/lms/lms/doctype/lms_course/lms_course.py b/lms/lms/doctype/lms_course/lms_course.py index c71568a0..057ab74c 100644 --- a/lms/lms/doctype/lms_course/lms_course.py +++ b/lms/lms/doctype/lms_course/lms_course.py @@ -7,7 +7,7 @@ import frappe from frappe.model.document import Document from frappe.utils import cint from frappe.utils.telemetry import capture -from lms.lms.utils import get_chapters +from lms.lms.utils import get_chapters, can_create_courses from ...utils import generate_slug, validate_image @@ -212,6 +212,9 @@ def save_course( upcoming, image=None, ): + if not can_create_courses(): + return + if course: doc = frappe.get_doc("LMS Course", course) else: diff --git a/lms/patches.txt b/lms/patches.txt index 2f455e83..eb99f28e 100644 --- a/lms/patches.txt +++ b/lms/patches.txt @@ -49,7 +49,6 @@ lms.patches.v0_0.rename_community_to_users #06-01-2023 lms.patches.v0_0.video_embed_link lms.patches.v0_0.rename_exercise_doctype lms.patches.v0_0.add_question_type #09-04-2023 -lms.patches.v0_0.add_evaluator_to_assignment #09-04-2023 lms.patches.v0_0.share_certificates execute:frappe.delete_doc("Web Form", "class", ignore_missing=True, force=True) lms.patches.v0_0.amend_course_and_lesson_editor_fields diff --git a/lms/patches/v0_0/add_evaluator_to_assignment.py b/lms/patches/v0_0/add_evaluator_to_assignment.py index c8146985..3556bd40 100644 --- a/lms/patches/v0_0/add_evaluator_to_assignment.py +++ b/lms/patches/v0_0/add_evaluator_to_assignment.py @@ -2,8 +2,9 @@ import frappe def execute(): - frappe.reload_doc("lms", "doctype", "lesson_assignment") - assignments = frappe.get_all("Lesson Assignment", fields=["name", "course"]) - for assignment in assignments: - evaluator = frappe.db.get_value("LMS Course", assignment.course, "evaluator") - frappe.db.set_value("Lesson Assignment", assignment.name, "evaluator", evaluator) + if frappe.db.exists("DocType", "Lesson Assignment"): + frappe.reload_doc("lms", "doctype", "lesson_assignment") + assignments = frappe.get_all("Lesson Assignment", fields=["name", "course"]) + for assignment in assignments: + evaluator = frappe.db.get_value("LMS Course", assignment.course, "evaluator") + frappe.db.set_value("Lesson Assignment", assignment.name, "evaluator", evaluator) diff --git a/lms/public/css/style.css b/lms/public/css/style.css index 5ac363db..6b7f6baa 100644 --- a/lms/public/css/style.css +++ b/lms/public/css/style.css @@ -4,6 +4,10 @@ --text-4xl: 36px; } +body { + font-size: var(--text-base); +} + .nav-link .course-list-count { border-radius: var(--border-radius-md); padding: 0 0.3rem; @@ -86,6 +90,14 @@ outline: none; } +.field-input .form-control { + color: initial; + background-color: inherit; + padding: 0; + height: inherit; + cursor: pointer; +} + .field-group { margin-bottom: 1.5rem; } @@ -103,11 +115,26 @@ } .image-preview { - width: 280px; - height: 178px; - border-radius: var(--border-radius-sm); + width: 100%; + height: 100%; + border: none; +} + +.file-source-preview { + display: flex; + align-items: center; + justify-content: center; + position: relative; + width: 100%; + height: 250px; border: 1px solid var(--gray-300); - margin-top: 1rem; + border-radius: var(--border-radius-md); +} + +.file-source-preview .btn-close { + position: absolute; + top: 5%; + right: 5%; } textarea.field-input { @@ -119,7 +146,7 @@ textarea.field-input { border-bottom: 1px solid var(--gray-300); } -.common-card-style .outline-lesson:last-of-type { +.outline-lesson:last-of-type { border-bottom: none; } @@ -173,6 +200,7 @@ textarea.field-input { .clickable { color: var(--gray-900); font-weight: 500; + cursor: pointer; } .clickable:hover { @@ -328,7 +356,6 @@ input[type=checkbox] { .icon { margin: 0; - margin-right: 0.25rem; } .lesson-links .icon { @@ -1876,10 +1903,6 @@ li { padding: 0 1rem !important; } -.modal-content { - font-size: var(--text-sm) !important; -} - .modal-header, .modal-body { margin-bottom: 0.5rem !important; } @@ -2155,4 +2178,8 @@ select { .list-row:last-child { border-bottom: none; +} + +.fill-width-twice { + flex: 2; } \ No newline at end of file diff --git a/lms/www/assignment_grading/__init__.py b/lms/www/assignment_grading/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lms/www/assignment_grading/assignment_grading.html b/lms/www/assignment_grading/assignment_grading.html new file mode 100644 index 00000000..eebfee2f --- /dev/null +++ b/lms/www/assignment_grading/assignment_grading.html @@ -0,0 +1,80 @@ +{% extends "templates/base.html" %} +{% block title %} + {{ _("Assignments") }} +{% endblock %} + + +{% block content %} +
+
+
+
{{ _("Assignment Grading") }}
+ {{ AssignmentForm(assignment) }} +
+
+
+ +{% endblock %} + + +{% macro AssignmentForm(assignment) %} +{% set course_title = frappe.db.get_value("LMS Course", assignment.course, "title") %} +{% set lesson_title = frappe.db.get_value("Course Lesson", assignment.lesson, "title") %} + +
+ +
+
+
+ + +
+
+ + +
+
+ +
+
+
+ + +
+ +
+ +
+ +
+ + + +
+
+
+
+ +
+ + +
+
+
+ +
+ + {{ _("Open Attachment") }} + + +
+
+{% endmacro %} diff --git a/lms/www/assignment_grading/assignment_grading.js b/lms/www/assignment_grading/assignment_grading.js new file mode 100644 index 00000000..8c16136e --- /dev/null +++ b/lms/www/assignment_grading/assignment_grading.js @@ -0,0 +1,55 @@ +frappe.ready(() => { + this.result; + let self = this; + + set_result(); + + $("#save-assignment").click((e) => { + save_assignment(e); + }); + + $("#result").change((e) => { + $("#result option:selected").each(function () { + self.result = $(this).val(); + }); + }); +}); + +const set_result = () => { + let self = this; + let result = $("#result").data("type"); + if (result) { + $("#result option").each((i, elem) => { + if ($(elem).val() == result) { + $(elem).attr("selected", true); + self.result = result; + } + }); + } +}; + +const save_assignment = (e) => { + e.preventDefault(); + if (!["Pass", "Fail"].includes(this.result)) + frappe.throw({ + title: __("Not Graded"), + message: __("Please grade the assignment."), + }); + frappe.call({ + method: "lms.lms.doctype.lms_assignment_submission.lms_assignment_submission.grade_assignment", + args: { + name: $(e.currentTarget).data("assignment"), + result: this.result, + comments: $("#comments").val(), + }, + callback: (data) => { + frappe.show_alert({ + message: __("Saved"), + indicator: "green", + }); + setTimeout(() => { + window.history.go(-2); + }, 2000); + }, + }); +}; diff --git a/lms/www/assignment_grading/assignment_grading.py b/lms/www/assignment_grading/assignment_grading.py new file mode 100644 index 00000000..ab9c8233 --- /dev/null +++ b/lms/www/assignment_grading/assignment_grading.py @@ -0,0 +1,35 @@ +import frappe +from lms.lms.utils import has_course_moderator_role +from frappe import _ + + +def get_context(context): + context.no_cache = 1 + assignment = frappe.form_dict["assignment"] + + context.assignment = frappe.db.get_value( + "LMS Assignment Submission", + assignment, + [ + "assignment", + "comments", + "status", + "name", + "member", + "member_name", + "course", + "lesson", + ], + as_dict=True, + ) + context.is_moderator = has_course_moderator_role() + + if ( + not has_course_moderator_role() + and not frappe.session.user == context.assignment.member + ): + message = "You don't have the permissions to access this page." + if frappe.session.user == "Guest": + message = "Please login to access this page." + + raise frappe.PermissionError(_(message)) diff --git a/lms/www/assignment_submission/__init__.py b/lms/www/assignment_submission/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lms/www/assignment_submission/assignment_submission.html b/lms/www/assignment_submission/assignment_submission.html new file mode 100644 index 00000000..d6f51016 --- /dev/null +++ b/lms/www/assignment_submission/assignment_submission.html @@ -0,0 +1,72 @@ +{% extends "lms/templates/lms_base.html" %} +{% block title %} + {{ assignment.title }} +{% endblock %} + +{% block page_content %} +
+ {{ Header() }} +
+ {{ SubmissionForm(assignment) }} +
+
+{% endblock %} + +{% macro Header() %} +
+
+
+
+
+ {{ assignment.title }} +
+ {% if submission.status %} + {% set color = "green" if submission.status == "Pass" else "red" if submission.status == "Fail" else "orange" %} +
+ {{ submission.status }} +
+ {% endif %} +
+ + {% if submission.status == "Not Graded" %} +
+ +
+ {% endif %} +
+
+
+{% endmacro %} + +{% macro SubmissionForm(assignment) %} +
+
+
+ {{ _("Question")}} +
+ {{ assignment.question }} +
+ +
+
+ {{ _("Submit your assignment")}} +
+ +
+ {% if submission.status == "Not Graded" %} + + {{ _("Clear") }} + + {% endif %} +
+ {{ _("Upload a {0}").format(assignment.type) }} +
+ +
+
+ + +
+{% endmacro %} \ No newline at end of file diff --git a/lms/www/assignment_submission/assignment_submission.js b/lms/www/assignment_submission/assignment_submission.js new file mode 100644 index 00000000..5da67659 --- /dev/null +++ b/lms/www/assignment_submission/assignment_submission.js @@ -0,0 +1,74 @@ +frappe.ready(() => { + $(".btn-upload").click((e) => { + upload_file(e); + }); + + $(".btn-save-assignment").click((e) => { + save_assignment(e); + }); + + $(".btn-close").click((e) => { + clear_preview(e); + }); +}); + +const upload_file = (e) => { + let type = $(e.currentTarget).data("type"); + let mapper = { + Image: "image/*", + Document: + ".doc,.docx,.xml,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document", + PDF: ".pdf", + }; + + new frappe.ui.FileUploader({ + disable_file_browser: true, + folder: "Home/Attachments", + make_attachments_public: true, + restrictions: { + allowed_file_types: [mapper[type]], + }, + on_success: (file_doc) => { + $(e.currentTarget).addClass("hide"); + $(".file-source-preview .btn-close").removeClass("hide"); + $(".file-source-preview iframe") + .attr("src", file_doc.file_url) + .removeClass("hide"); + }, + }); +}; + +const save_assignment = (e) => { + let file = $(".image-preview").attr("src"); + if (!file) { + frappe.throw({ + title: __("No File"), + message: __("Please upload a file."), + }); + } + + frappe.call({ + method: "lms.lms.doctype.lms_assignment_submission.lms_assignment_submission.upload_assignment", + args: { + assignment: $(e.currentTarget).data("assignment"), + assignment_attachment: file, + }, + callback: (data) => { + frappe.show_alert({ + message: __("Saved"), + indicator: "green", + }); + setTimeout(() => { + window.location.href = `/assignment-submission/${$( + e.currentTarget + ).data("assignment")}/${data.message}`; + }, 2000); + }, + }); +}; + +const clear_preview = (e) => { + $(".file-source-preview .btn-upload").removeClass("hide"); + $(".file-source-preview iframe").attr("src", "").addClass("hide"); + $(".file-source-preview .btn-close").addClass("hide"); +}; diff --git a/lms/www/assignment_submission/assignment_submission.py b/lms/www/assignment_submission/assignment_submission.py new file mode 100644 index 00000000..186b7df5 --- /dev/null +++ b/lms/www/assignment_submission/assignment_submission.py @@ -0,0 +1,24 @@ +import frappe +from frappe import _ + + +def get_context(context): + context.no_cache = 1 + submission = frappe.form_dict["submission"] + assignment = frappe.form_dict["assignment"] + context.assignment = frappe.db.get_value( + "LMS Assignment", assignment, ["title", "name", "type", "question"], as_dict=1 + ) + + if submission == "new-submission": + context.submission = frappe._dict() + else: + context.submission = frappe.db.get_value( + "LMS Assignment Submission", + submission, + ["name", "assignment_attachment", "comments", "status"], + as_dict=True, + ) + + if not context.assignment or not context.submission: + raise frappe.PermissionError(_("Invalid Submission URL")) diff --git a/lms/www/assignments/assignment.html b/lms/www/assignments/assignment.html index eebfee2f..cf2d0135 100644 --- a/lms/www/assignments/assignment.html +++ b/lms/www/assignments/assignment.html @@ -1,80 +1,99 @@ -{% extends "templates/base.html" %} +{% extends "lms/templates/lms_base.html" %} {% block title %} - {{ _("Assignments") }} + {{ assignment.title if assignment.name else _("Assignment Details") }} {% endblock %} - -{% block content %} +{% block page_content %}
-
-
-
{{ _("Assignment Grading") }}
- {{ AssignmentForm(assignment) }} -
-
+ {{ Header() }} +
+ {{ AssignmentForm(assignment) }} +
- {% endblock %} +{% macro Header() %} +
+
+
+
+
+ {{ _("Assignment Details") }} +
+
+ + {{ _("Assignment List") }} + + + {{ assignment.title if assignment.title else _("New Assignment") }} +
+
+ +
+ +
+ +
+
+
+{% endmacro %} {% macro AssignmentForm(assignment) %} -{% set course_title = frappe.db.get_value("LMS Course", assignment.course, "title") %} -{% set lesson_title = frappe.db.get_value("Course Lesson", assignment.lesson, "title") %} +
+
+
+
{{ _("Title") }}
+
+ {{ _("Give the assignment a title.") }} +
+ +
-
+
+
{{ "Type" }}
+
+ {{ _("Select the format in which students will have to submit the assignment.") }} +
+
+ +
+ + + +
+
-
-
-
- - -
-
- - -
-
+
-
-
-
- - -
- -
- -
- -
- - - -
-
-
-
- -
- - -
-
-
- -
- - {{ _("Open Attachment") }} - - -
-
+
+
+ {{ _("Question") }} +
+
+ {{ _("Enter an assignment question.") }} +
+
+ {% if assignment.question %} +
+ {{ assignment.question }} +
+ {% endif %} +
+
+
{% endmacro %} + + +{%- block script %} + {{ super() }} + {{ include_script('controls.bundle.js') }} +{% endblock %} \ No newline at end of file diff --git a/lms/www/assignments/assignment.js b/lms/www/assignments/assignment.js index 8c16136e..1d55034e 100644 --- a/lms/www/assignments/assignment.js +++ b/lms/www/assignments/assignment.js @@ -1,46 +1,38 @@ frappe.ready(() => { - this.result; - let self = this; + if ($("#question").length) { + make_editor(); + } - set_result(); - - $("#save-assignment").click((e) => { + $(".btn-save-assignment").click((e) => { save_assignment(e); }); - - $("#result").change((e) => { - $("#result option:selected").each(function () { - self.result = $(this).val(); - }); - }); }); -const set_result = () => { - let self = this; - let result = $("#result").data("type"); - if (result) { - $("#result option").each((i, elem) => { - if ($(elem).val() == result) { - $(elem).attr("selected", true); - self.result = result; - } - }); - } +const make_editor = () => { + this.question = new frappe.ui.FieldGroup({ + fields: [ + { + fieldname: "question", + fieldtype: "Text Editor", + default: $("#question-data").html(), + }, + ], + body: $("#question").get(0), + }); + this.question.make(); + $("#question .form-section:last").removeClass("empty-section"); + $("#question .frappe-control").removeClass("hide-control"); + $("#question .form-column").addClass("p-0"); }; const save_assignment = (e) => { - e.preventDefault(); - if (!["Pass", "Fail"].includes(this.result)) - frappe.throw({ - title: __("Not Graded"), - message: __("Please grade the assignment."), - }); frappe.call({ - method: "lms.lms.doctype.lms_assignment_submission.lms_assignment_submission.grade_assignment", + method: "lms.lms.doctype.lms_assignment.lms_assignment.save_assignment", args: { - name: $(e.currentTarget).data("assignment"), - result: this.result, - comments: $("#comments").val(), + assignment: $(e.currentTarget).data("assignment") || "", + title: $("#title").val(), + question: this.question.fields_dict["question"].value, + type: $("#type").val(), }, callback: (data) => { frappe.show_alert({ @@ -48,7 +40,7 @@ const save_assignment = (e) => { indicator: "green", }); setTimeout(() => { - window.history.go(-2); + window.location.href = `/assignments/${data.message}`; }, 2000); }, }); diff --git a/lms/www/assignments/assignment.py b/lms/www/assignments/assignment.py index ab9c8233..9b1c299d 100644 --- a/lms/www/assignments/assignment.py +++ b/lms/www/assignments/assignment.py @@ -1,35 +1,23 @@ import frappe -from lms.lms.utils import has_course_moderator_role from frappe import _ +from lms.lms.utils import can_create_courses def get_context(context): context.no_cache = 1 - assignment = frappe.form_dict["assignment"] - context.assignment = frappe.db.get_value( - "LMS Assignment Submission", - assignment, - [ - "assignment", - "comments", - "status", - "name", - "member", - "member_name", - "course", - "lesson", - ], - as_dict=True, - ) - context.is_moderator = has_course_moderator_role() - - if ( - not has_course_moderator_role() - and not frappe.session.user == context.assignment.member - ): - message = "You don't have the permissions to access this page." + if not can_create_courses(): + message = "You do not have permission to access this page." if frappe.session.user == "Guest": message = "Please login to access this page." raise frappe.PermissionError(_(message)) + + assignment = frappe.form_dict["assignment"] + + if assignment == "new-assignment": + context.assignment = frappe._dict() + else: + context.assignment = frappe.db.get_value( + "LMS Assignment", assignment, ["title", "name", "type", "question"], as_dict=1 + ) diff --git a/lms/www/assignments/index.html b/lms/www/assignments/index.html new file mode 100644 index 00000000..e04bc3f3 --- /dev/null +++ b/lms/www/assignments/index.html @@ -0,0 +1,64 @@ +{% extends "templates/base.html" %} +{% block title %} + {{ _("Assignment List") }} +{% endblock %} + +{% block content %} +
+
+ {{ Header() }} + {% if assignments | length %} + {{ AssignmentList(assignments) }} + {% else %} + {{ EmptyState() }} + {% endif %} +
+
+{% endblock %} + +{% macro Header() %} +
+
+
+ {{ _("Assignment List") }} +
+ + + {{ _("Add Assignment") }} + +
+
+{% endmacro %} + +{% macro AssignmentList(assignments) %} +
+ +
+{% endmacro %} + +{% macro EmptyState() %} +
+ +
+
+ {{ _("You have not created any assignment yet.") }} +
+
+ {{ _("Create an assignment and evaluate your students.") }} +
+
+
+{% endmacro %} diff --git a/lms/www/assignments/index.py b/lms/www/assignments/index.py new file mode 100644 index 00000000..528b92f8 --- /dev/null +++ b/lms/www/assignments/index.py @@ -0,0 +1,10 @@ +import frappe + + +def get_context(context): + context.no_cache = 1 + context.assignments = frappe.get_all( + "LMS Assignment", + {"owner": frappe.session.user}, + ["title", "name", "type", "question"], + ) diff --git a/lms/www/batch/edit.js b/lms/www/batch/edit.js index 3b45ced5..306b9580 100644 --- a/lms/www/batch/edit.js +++ b/lms/www/batch/edit.js @@ -6,7 +6,7 @@ frappe.ready(() => { } setup_editor(); - fetch_quiz_list(); + //fetch_quiz_list(); $("#save-lesson").click((e) => { save_lesson(e); diff --git a/lms/www/batch/quiz.py b/lms/www/batch/quiz.py index 2e8bfb9e..b24c60ed 100644 --- a/lms/www/batch/quiz.py +++ b/lms/www/batch/quiz.py @@ -17,7 +17,6 @@ def get_context(context): quizname = frappe.form_dict["quizname"] if quizname == "new-quiz": context.quiz = frappe._dict() - context.quiz.edit_mode = 1 else: fields_arr = ["name", "question", "type"] for num in range(1, 5): diff --git a/lms/www/classes/class.html b/lms/www/classes/class.html index e115cb31..32bd2b37 100644 --- a/lms/www/classes/class.html +++ b/lms/www/classes/class.html @@ -88,9 +88,21 @@ {% if class_students | length and (is_moderator or is_student) %} + + {% endif %} @@ -109,6 +121,10 @@ {% if class_students | length and (is_moderator or is_student) %} +
+ {{ AssessmentsSection(class_info) }} +
+
{{ LiveClassSection(class_info, live_classes) }}
@@ -159,7 +175,6 @@ - {% endmacro %} @@ -182,8 +197,7 @@
{% for student in class_students %} {% set last_active = frappe.db.get_value("User", student.student, "last_active") %} - {% set allow_progress = is_moderator or student.student == frappe.session.user or (frappe.db.get_single_value("LMS Settings", "allow_student_progress") and - is_student) %} + {% set allow_progress = is_moderator or student.student == frappe.session.user %}
@@ -209,6 +223,115 @@ {% endmacro %} +{% macro AssessmentsSection(class_info) %} +
+
+
+ {{ _("Assessments") }} +
+ {% if is_moderator %} + + {% endif %} +
+ {{ CreateAssessment() }} + {{ AssessmentList(assessments) }} +
+{% endmacro %} + + +{% macro CreateAssessment() %} +{% if is_moderator %} + +{% endif %} +{% endmacro %} + +{% macro AssessmentList(assessments) %} +{% if assessments | length %} +
+
+
+ {{ _("Title") }} +
+
+ {{ _("Type") }} +
+
+ {% for assessment in assessments %} +
+ +
+ {{ assessment.assessment_type.split("LMS ")[1] }} +
+
+ {% endfor %} +
+ {% else %} +

{{ _("No Assessments") }}

+ {% endif %} +{% endmacro %} + + {% macro LiveClassSection(class_info, live_classes) %}
@@ -230,7 +353,6 @@ {% macro CreateLiveClass(class_info) %} {% if is_moderator %} -