diff --git a/lms/hooks.py b/lms/hooks.py index d838d31e..07b8d74b 100644 --- a/lms/hooks.py +++ b/lms/hooks.py @@ -180,6 +180,10 @@ website_route_rules = [ "to_route": "/classes/progress", }, {"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/lesson_assignment/__init__.py b/lms/lms/doctype/lms_assessment/__init__.py similarity index 100% rename from lms/lms/doctype/lesson_assignment/__init__.py rename to lms/lms/doctype/lms_assessment/__init__.py 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..647833de --- /dev/null +++ b/lms/lms/doctype/lms_assignment/lms_assignment.py @@ -0,0 +1,25 @@ +# 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/__init__.py b/lms/lms/doctype/lms_assignment_submission/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lms/lms/doctype/lesson_assignment/lesson_assignment.js b/lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.js similarity index 73% rename from lms/lms/doctype/lesson_assignment/lesson_assignment.js rename to lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.js index de578610..d6aec59c 100644 --- a/lms/lms/doctype/lesson_assignment/lesson_assignment.js +++ b/lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.js @@ -1,7 +1,7 @@ // Copyright (c) 2021, Frappe and contributors // For license information, please see license.txt -frappe.ui.form.on("Lesson Assignment", { +frappe.ui.form.on("LMS Assignment Submission", { // refresh: function(frm) { // } }); diff --git a/lms/lms/doctype/lesson_assignment/lesson_assignment.json b/lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.json similarity index 61% rename from lms/lms/doctype/lesson_assignment/lesson_assignment.json rename to lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.json index 36957671..0445a09f 100644 --- a/lms/lms/doctype/lesson_assignment/lesson_assignment.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 }, { @@ -83,15 +92,59 @@ "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.696868", + "modified": "2023-05-30 16:10:09.173258", "modified_by": "Administrator", "module": "LMS", - "name": "Lesson Assignment", + "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/lesson_assignment/lesson_assignment.py b/lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.py similarity index 54% rename from lms/lms/doctype/lesson_assignment/lesson_assignment.py rename to lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.py index e9c62f11..e69558c7 100644 --- a/lms/lms/doctype/lesson_assignment/lesson_assignment.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( - "Lesson Assignment", - {"lesson": self.lesson, "member": self.member, "name": ["!=", self.name]}, + "LMS Assignment Submission", + {"assignment": self.assignment, "member": self.member, "name": ["!=", self.name]}, ): lesson_title = frappe.db.get_value("Course Lesson", self.lesson, "title") frappe.throw( @@ -24,25 +24,44 @@ class LessonAssignment(Document): @frappe.whitelist() -def upload_assignment(assignment, lesson): - args = { - "doctype": "Lesson Assignment", - "lesson": lesson, - "member": frappe.session.user, - } - if frappe.db.exists(args): - del args["doctype"] - frappe.db.set_value("Lesson Assignment", args, "assignment", assignment) +def upload_assignment( + assignment_attachment, + assignment, + lesson=None, + status="Not Graded", + comments=None, + submission=None, +): + if frappe.session.user == "Guest": + return + + if submission: + doc = frappe.get_doc("LMS Assignment Submission", submission) else: - args.update({"assignment": assignment}) - lesson_work = frappe.get_doc(args) - lesson_work.save(ignore_permissions=True) + doc = frappe.get_doc( + { + "doctype": "LMS Assignment Submission", + "assignment": assignment, + "lesson": lesson, + "member": frappe.session.user, + } + ) + + doc.update( + { + "assignment_attachment": assignment_attachment, + "status": status, + "comments": comments, + } + ) + doc.save(ignore_permissions=True) + return doc.name @frappe.whitelist() def get_assignment(lesson): assignment = frappe.db.get_value( - "Lesson Assignment", + "LMS Assignment Submission", {"lesson": lesson, "member": frappe.session.user}, ["lesson", "member", "assignment", "comments", "status"], as_dict=True, @@ -55,7 +74,7 @@ def get_assignment(lesson): @frappe.whitelist() def grade_assignment(name, result, comments): - doc = frappe.get_doc("Lesson Assignment", name) + doc = frappe.get_doc("LMS Assignment Submission", name) doc.status = result doc.comments = comments doc.save(ignore_permissions=True) diff --git a/lms/lms/doctype/lesson_assignment/test_lesson_assignment.py b/lms/lms/doctype/lms_assignment_submission/test_lms_assignment_submission.py similarity index 66% rename from lms/lms/doctype/lesson_assignment/test_lesson_assignment.py rename to lms/lms/doctype/lms_assignment_submission/test_lms_assignment_submission.py index b8792556..27478506 100644 --- a/lms/lms/doctype/lesson_assignment/test_lesson_assignment.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_batch/lms_batch.py b/lms/lms/doctype/lms_batch/lms_batch.py index b2d13a79..6e005024 100644 --- a/lms/lms/doctype/lms_batch/lms_batch.py +++ b/lms/lms/doctype/lms_batch/lms_batch.py @@ -80,7 +80,6 @@ def switch_batch(course_name, email, batch_name): old_batch = frappe.get_doc("LMS Batch", membership.batch) - print("updating membership", membership.name) membership.batch = batch_name membership.save() 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..abca9c15 100644 --- a/lms/lms/doctype/lms_class/lms_class.py +++ b/lms/lms/doctype/lms_class/lms_class.py @@ -180,3 +180,23 @@ 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: + doc = frappe.new_doc("LMS Assessment") + doc.update(filters) + doc.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/lms/notification/assignment_submission_notification/assignment_submission_notification.json b/lms/lms/notification/assignment_submission_notification/assignment_submission_notification.json index c57ee791..03a91be8 100644 --- a/lms/lms/notification/assignment_submission_notification/assignment_submission_notification.json +++ b/lms/lms/notification/assignment_submission_notification/assignment_submission_notification.json @@ -1,11 +1,11 @@ { "attach_print": 0, "channel": "Email", - "creation": "2023-03-27 16:34:03.505645", + "creation": "2023-03-27 16:34:03.505647", "days_in_advance": 0, "docstatus": 0, "doctype": "Notification", - "document_type": "Lesson Assignment", + "document_type": "LMS Assignment Submission", "enabled": 1, "event": "New", "idx": 0, diff --git a/lms/patches.txt b/lms/patches.txt index dcd27ed3..eb99f28e 100644 --- a/lms/patches.txt +++ b/lms/patches.txt @@ -49,8 +49,8 @@ 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 lms.patches.v0_0.convert_course_description_to_html #11-05-2023 +lms.patches.v1_0.rename_assignment_doctype \ No newline at end of file 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/patches/v1_0/rename_assignment_doctype.py b/lms/patches/v1_0/rename_assignment_doctype.py new file mode 100644 index 00000000..a0c164e4 --- /dev/null +++ b/lms/patches/v1_0/rename_assignment_doctype.py @@ -0,0 +1,13 @@ +import frappe +from frappe.model.rename_doc import rename_doc + + +def execute(): + if frappe.db.exists("DocType", "LMS Assignment Submission"): + return + + frappe.flags.ignore_route_conflict_validation = True + rename_doc("DocType", "Lesson Assignment", "LMS Assignment Submission") + frappe.flags.ignore_route_conflict_validation = False + + frappe.reload_doctype("LMS Assignment Submission", force=True) diff --git a/lms/public/css/style.css b/lms/public/css/style.css index 5ac363db..23b64b1a 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; @@ -71,7 +75,7 @@ .field-label { color: var(--gray-900); - font-weight: 600; + font-weight: 500; } .field-input { @@ -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; } @@ -105,7 +117,7 @@ .image-preview { width: 280px; height: 178px; - border-radius: var(--border-radius-sm); + border-radius: var(--border-radius-md); border: 1px solid var(--gray-300); margin-top: 1rem; } @@ -119,7 +131,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 +185,7 @@ textarea.field-input { .clickable { color: var(--gray-900); font-weight: 500; + cursor: pointer; } .clickable:hover { @@ -328,7 +341,6 @@ input[type=checkbox] { .icon { margin: 0; - margin-right: 0.25rem; } .lesson-links .icon { @@ -1876,10 +1888,6 @@ li { padding: 0 1rem !important; } -.modal-content { - font-size: var(--text-sm) !important; -} - .modal-header, .modal-body { margin-bottom: 0.5rem !important; } @@ -1890,13 +1898,11 @@ li { .modal-footer { padding: 0.75rem 1.5rem !important; - border-top: none !important; - background-color: var(--gray-50) !important; justify-content: flex-end !important; } -.modal-footer .btn:first-child { - margin-right: 0.5rem; +.modal-footer .btn-primary { + margin-left: 0.5rem } .modal-header .modal-title { 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..6afb2192 --- /dev/null +++ b/lms/www/assignment_submission/assignment_submission.html @@ -0,0 +1,133 @@ +{% 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 %} +
+ +
+ + +
+ +
+
+
+
+{% endmacro %} + +{% macro SubmissionForm(assignment) %} +
+ {% if submission.name and is_moderator %} +
+
+ {{ _("Student Name") }} +
+ {{ submission.member_name }} +
+ {% endif %} + +
+
+ {{ _("Question")}} +
+ {{ assignment.question }} +
+ +
+
+ {{ _("Submit")}} +
+
+ {{ _("Upload assignment as {0}").format(assignment.type) }} +
+ + +
+ {{ _("Browse").format(assignment.type) }} +
+ +
+ + {% if is_moderator %} +
+
+ {{ _("Status") }} +
+
+ +
+ + + +
+
+
+ +
+
+ {{ _("Comments by Mentor") }} +
+ +
+ {% endif %} + + {% if submission and submission.member == frappe.session.user and submission.comments %} +
+
+ {{ _("Comments by Mentor") }} +
+
+ {{ submission.comments }} +
+
+ {% endif %} + +
+{% 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..a46f054f --- /dev/null +++ b/lms/www/assignment_submission/assignment_submission.js @@ -0,0 +1,86 @@ +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"); + $("#assignment-preview").removeClass("hide"); + $("#assignment-preview .btn-close").removeClass("hide"); + $("#assignment-preview a").attr( + "href", + encodeURI(file_doc.file_url) + ); + $("#assignment-preview a").text(file_doc.file_url); + }, + }); +}; + +const save_assignment = (e) => { + let file = $("#assignment-preview a").attr("href"); + 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"), + submission: $(e.currentTarget).data("submission") || "", + assignment_attachment: file, + status: $("#status").val(), + comments: $("#comments").val(), + }, + 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) => { + $(".btn-upload").removeClass("hide"); + $("#assignment-preview").addClass("hide"); + $("#assignment-preview a").attr("href", ""); + $("#assignment-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..3f903099 --- /dev/null +++ b/lms/www/assignment_submission/assignment_submission.py @@ -0,0 +1,29 @@ +import frappe +from frappe import _ +from lms.lms.utils import has_course_moderator_role + + +def get_context(context): + context.no_cache = 1 + context.is_moderator = has_course_moderator_role() + 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", "member", "member_name"], + as_dict=True, + ) + if not context.is_moderator and frappe.session.user != context.submission.member: + raise frappe.PermissionError(_("You don't have permission to access this page.")) + + 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..ee9a66a8 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 418e7567..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.lesson_assignment.lesson_assignment.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 33823012..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( - "Lesson Assignment", - 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/learn.js b/lms/www/batch/learn.js index 873558ef..1a6164c0 100644 --- a/lms/www/batch/learn.js +++ b/lms/www/batch/learn.js @@ -381,7 +381,7 @@ const upload_file = (file, target) => { const create_lesson_work = (file, target) => { frappe.call({ - method: "lms.lms.doctype.lesson_assignment.lesson_assignment.upload_assignment", + method: "lms.lms.doctype.lms_assignment_submission.lms_assignment_submission.upload_assignment", args: { assignment: file.file_url, lesson: $(".title").attr("data-lesson"), @@ -442,7 +442,7 @@ const clear_work = (e) => { const fetch_assignments = () => { if ($(".attach-file").length <= 0) return; frappe.call({ - method: "lms.lms.doctype.lesson_assignment.lesson_assignment.get_assignment", + method: "lms.lms.doctype.lms_assignment_submission.lms_assignment_submission.get_assignment", args: { lesson: $(".title").attr("data-lesson"), }, diff --git a/lms/www/batch/quiz.py b/lms/www/batch/quiz.py index 5d687fea..b24c60ed 100644 --- a/lms/www/batch/quiz.py +++ b/lms/www/batch/quiz.py @@ -1,13 +1,22 @@ import frappe from frappe.utils import cstr +from frappe import _ +from lms.lms.utils import can_create_courses def get_context(context): context.no_cache = 1 + + 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)) + 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/batch/quiz_list.html b/lms/www/batch/quiz_list.html index 77898384..c53881bd 100644 --- a/lms/www/batch/quiz_list.html +++ b/lms/www/batch/quiz_list.html @@ -35,11 +35,16 @@
diff --git a/lms/www/batch/quiz_list.py b/lms/www/batch/quiz_list.py index 6208525d..ade57b69 100644 --- a/lms/www/batch/quiz_list.py +++ b/lms/www/batch/quiz_list.py @@ -1,8 +1,18 @@ import frappe +from lms.lms.utils import can_create_courses +from frappe import _ def get_context(context): context.no_cache = 1 + + 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)) + context.quiz_list = frappe.get_all( "LMS Quiz", {"owner": frappe.session.user}, ["name", "title"] ) diff --git a/lms/www/classes/class.html b/lms/www/classes/class.html index e115cb31..6822afd4 100644 --- a/lms/www/classes/class.html +++ b/lms/www/classes/class.html @@ -87,10 +87,24 @@ + {% if is_moderator %} + + {% endif %} + {% if class_students | length and (is_moderator or is_student) %} {% endif %} @@ -108,6 +122,12 @@ {{ StudentsSection(class_info, class_students) }} + {% if is_moderator %} +
+ {{ AssessmentsSection(class_info) }} +
+ {% endif %} + {% if class_students | length and (is_moderator or is_student) %}
{{ LiveClassSection(class_info, live_classes) }} @@ -138,9 +158,9 @@
{% for course in class_courses %}
- + {% if is_moderator %}
@@ -159,7 +179,6 @@ - {% endmacro %} @@ -182,8 +201,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 +227,127 @@ {% 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 +369,6 @@ {% macro CreateLiveClass(class_info) %} {% if is_moderator %} -
{% endmacro %} -{% macro ClassTabs(past_classes, upcoming_classes) %} +{% macro ClassTabs(past_classes, upcoming_classes, my_classes) %}
+ {% if is_moderator %}
{{ ClassCards(past_classes) }}
+ {% endif %} + + {% if frappe.session.user != "Guest" %} +
+ {{ ClassCards(my_classes) }} +
+ {% endif %}
diff --git a/lms/www/classes/index.py b/lms/www/classes/index.py index 4e387678..8f44a81a 100644 --- a/lms/www/classes/index.py +++ b/lms/www/classes/index.py @@ -13,12 +13,28 @@ def get_context(context): past_classes, upcoming_classes = [], [] for class_ in classes: - print(class_.start_date) if getdate(class_.start_date) < getdate(): past_classes.append(class_) else: upcoming_classes.append(class_) context.past_classes = sorted(past_classes, key=lambda d: d.start_date) - context.upcoming_classes = sorted(upcoming_classes, key=lambda d: d.start_date) + + if frappe.session.user != "Guest": + my_classes_info = [] + my_classes = frappe.get_all( + "Class Student", {"student": frappe.session.user}, pluck="parent" + ) + + for class_ in my_classes: + my_classes_info.append( + frappe.db.get_value( + "LMS Class", + class_, + ["name", "title", "start_date", "end_date", "paid_class", "seat_count"], + as_dict=True, + ) + ) + + context.my_classes = my_classes_info diff --git a/lms/www/classes/progress.html b/lms/www/classes/progress.html index 07fc6b40..4f7be796 100644 --- a/lms/www/classes/progress.html +++ b/lms/www/classes/progress.html @@ -6,179 +6,74 @@ {% block page_content %}
-
- {{ BreadCrumb(class_info, student) }} -
-
-
- - {{ frappe.utils.format_datetime(student.last_active, "medium") }} - - {% if is_moderator %} - - {{ _("Evaluate") }} - - {% endif %} -
- - {{ student.full_name }} - -
- {{ Progress(class_courses, student) }} -
+ {{ Header() }} +
+ {{ Progress(class_info, student) }}
{% endblock %} +{% macro Header() %} +
+
+
+
+
+ {{ _("{0}'s Progress").format(student.full_name) }} +
+
+ + {{ _("All Classes") }} + + + + {{ class_info.name }} + + + {{ _("{0}'s Progress").format(student.full_name) }} +
+
-{% macro BreadCrumb(class_info, student) %} - + {% if is_moderator %} + + {% endif %} + +
+
+
{% endmacro %} {% macro Progress(class_info, student) %} -
- {% for course in class_courses %} - {% set progress = course.membership.progress %} -
-
-
{{ course.title }}
-
{{ frappe.utils.cint(course.membership.progress) }}%
-
+{% if assessments | length %} +
+ {% for assessment in assessments %} +
+ + {{ assessment.title }} + - {% if course.quizzes | length or course.assignments | length or course.evaluations | length %} -
- - - - - - - - - {{ Quiz(course, student) }} - {{ Assignment(course, student, is_moderator) }} - {{ Evaluation(course, student, is_moderator) }} - -
- {{ _("Type") }} - - {{ _("Title") }} - - {{ _("Score/Status") }} - - {{ _("Last Attempt Date") }} -
-
+ {% if assessment.submission %} + {% set status = assessment.submission.status %} + {% set color = "green" if status == "Pass" else "red" if status == "Fail" else "orange" %} +
+
+ {{ assessment.submission.status }} +
+
{% else %} -
- {{ _("There are no activities in this course.") }} -
+
+ {{ _("Not Attempted") }} +
{% endif %}
{% endfor %} -
-{% endmacro %} - - -{% macro Quiz(course, student) %} - {% for quiz in course.quizzes %} - {% set filters = { "member": student.name, "course": course.course } %} - {% set has_submitted = frappe.db.exists("LMS Quiz Submission", filters) %} - {% set submission = frappe.db.get_value("LMS Quiz Submission", filters, ["score", "creation"], as_dict=True) %} - {% set total_questions = frappe.db.count("LMS Quiz Question", {"parent": quiz.name}) %} - - - - - - - {{ _("Quiz") }} - - {{ quiz.title }} - {% if has_submitted %} - {{ submission.score }}/{{ total_questions }} - {{ frappe.utils.format_date(submission.creation, "medium") }} - {% else %} - - - -
- {{ _("Not Attempted") }} -
- - {% endif %} - - {% endfor %} -{% endmacro %} - - -{% macro Assignment(course, student, is_moderator) %} - {% for assignment in course.assignments %} - {% set filters = { "member": student.name, "course": course.course, "lesson": assignment.name } %} - {% set has_submitted = frappe.db.exists("Lesson Assignment", filters) %} - {% set submission = frappe.db.get_value("Lesson Assignment", filters, ["assignment", "creation", "status"], as_dict=True) %} - {% set status = submission.status %} - {% set color = "green" if status == "Pass" else "red" if status == "Fail" else "orange" %} - {% set can_see_details = has_submitted and (is_moderator or frappe.session.user == student.name) %} - - - - - - - {{ _("Assignment") }} - - - {{ assignment.title }} - - {% if has_submitted %} - -
- {{ status }} -
- - - {{ frappe.utils.format_date(submission.creation, "medium") }} - - {% else %} - - - - -
- {{ _("Not Attempted") }} -
- - {% endif %} - - {% endfor %} -{% endmacro %} - - -{% macro Evaluation(course, student, is_moderator) %} - {% for evaluation in course.evaluations %} - {% set color = "green" if evaluation.status == "Pass" else "red" %} - {% set can_see_details = is_moderator or frappe.session.user == student.name %} - - - - - - - {{ _("Evaluation") }} - - - - -
- {{ evaluation.status }} -
- - {{ frappe.utils.format_date(evaluation.creation, "medium") }} - - {% endfor %} -{% endmacro %} + +{% else %} +

{{ _("No Assessments") }}

+{% endif %} +{% endmacro %} \ No newline at end of file diff --git a/lms/www/classes/progress.py b/lms/www/classes/progress.py index df03849e..be922dd1 100644 --- a/lms/www/classes/progress.py +++ b/lms/www/classes/progress.py @@ -1,13 +1,14 @@ import frappe from lms.lms.utils import has_course_moderator_role from frappe import _ +from lms.www.utils import get_assessments def get_context(context): context.no_cache = 1 student = frappe.form_dict["username"] - classname = frappe.form_dict["classname"] + class_name = frappe.form_dict["classname"] context.is_moderator = has_course_moderator_role() context.student = frappe.db.get_value( @@ -17,32 +18,7 @@ def get_context(context): as_dict=True, ) context.class_info = frappe.db.get_value( - "LMS Class", classname, ["name"], as_dict=True + "LMS Class", class_name, ["name"], as_dict=True ) - class_courses = frappe.get_all( - "Class Course", {"parent": classname}, ["course", "title"] - ) - - for course in class_courses: - course.membership = frappe.db.get_value( - "LMS Batch Membership", - {"member": context.student.name, "course": course.course}, - ["progress"], - as_dict=True, - ) - course.quizzes = frappe.get_all( - "LMS Quiz", {"course": course.course}, ["name", "title"] - ) - course.assignments = frappe.get_all( - "Course Lesson", - {"course": course.course, "question": ["is", "set"]}, - ["name", "title"], - ) - course.evaluations = frappe.get_all( - "LMS Certificate Evaluation", - {"course": course.course, "member": context.student.name}, - ["rating", "status", "creation", "name"], - ) - - context.class_courses = class_courses + context.assessments = get_assessments(class_name, context.student.name) diff --git a/lms/www/courses/create.html b/lms/www/courses/create.html index 3fff7c13..716441e3 100644 --- a/lms/www/courses/create.html +++ b/lms/www/courses/create.html @@ -117,7 +117,7 @@