Merge pull request #520 from pateljannat/assignments-in-classes
feat: assignments in class
This commit is contained in:
@@ -180,6 +180,10 @@ website_route_rules = [
|
||||
"to_route": "/classes/progress",
|
||||
},
|
||||
{"from_route": "/assignments/<assignment>", "to_route": "assignments/assignment"},
|
||||
{
|
||||
"from_route": "/assignment-submission/<assignment>/<submission>",
|
||||
"to_route": "assignment_submission/assignment_submission",
|
||||
},
|
||||
]
|
||||
|
||||
website_redirects = [
|
||||
|
||||
40
lms/lms/doctype/lms_assessment/lms_assessment.json
Normal file
40
lms/lms/doctype/lms_assessment/lms_assessment.json
Normal file
@@ -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": []
|
||||
}
|
||||
9
lms/lms/doctype/lms_assessment/lms_assessment.py
Normal file
9
lms/lms/doctype/lms_assessment/lms_assessment.py
Normal file
@@ -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
|
||||
0
lms/lms/doctype/lms_assignment/__init__.py
Normal file
0
lms/lms/doctype/lms_assignment/__init__.py
Normal file
8
lms/lms/doctype/lms_assignment/lms_assignment.js
Normal file
8
lms/lms/doctype/lms_assignment/lms_assignment.js
Normal file
@@ -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) {
|
||||
|
||||
// },
|
||||
// });
|
||||
72
lms/lms/doctype/lms_assignment/lms_assignment.json
Normal file
72
lms/lms/doctype/lms_assignment/lms_assignment.json
Normal file
@@ -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": []
|
||||
}
|
||||
25
lms/lms/doctype/lms_assignment/lms_assignment.py
Normal file
25
lms/lms/doctype/lms_assignment/lms_assignment.py
Normal file
@@ -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
|
||||
9
lms/lms/doctype/lms_assignment/test_lms_assignment.py
Normal file
9
lms/lms/doctype/lms_assignment/test_lms_assignment.py
Normal file
@@ -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
|
||||
@@ -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) {
|
||||
// }
|
||||
});
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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",
|
||||
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:
|
||||
doc = frappe.get_doc(
|
||||
{
|
||||
"doctype": "LMS Assignment Submission",
|
||||
"assignment": assignment,
|
||||
"lesson": lesson,
|
||||
"member": frappe.session.user,
|
||||
}
|
||||
if frappe.db.exists(args):
|
||||
del args["doctype"]
|
||||
frappe.db.set_value("Lesson Assignment", args, "assignment", assignment)
|
||||
else:
|
||||
args.update({"assignment": assignment})
|
||||
lesson_work = frappe.get_doc(args)
|
||||
lesson_work.save(ignore_permissions=True)
|
||||
)
|
||||
|
||||
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)
|
||||
@@ -5,5 +5,5 @@
|
||||
import unittest
|
||||
|
||||
|
||||
class TestLessonAssignment(unittest.TestCase):
|
||||
class TestLMSAssignmentSubmission(unittest.TestCase):
|
||||
pass
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
@@ -2,6 +2,7 @@ import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
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:
|
||||
|
||||
13
lms/patches/v1_0/rename_assignment_doctype.py
Normal file
13
lms/patches/v1_0/rename_assignment_doctype.py
Normal file
@@ -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)
|
||||
@@ -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 {
|
||||
|
||||
0
lms/www/assignment_submission/__init__.py
Normal file
0
lms/www/assignment_submission/__init__.py
Normal file
133
lms/www/assignment_submission/assignment_submission.html
Normal file
133
lms/www/assignment_submission/assignment_submission.html
Normal file
@@ -0,0 +1,133 @@
|
||||
{% extends "lms/templates/lms_base.html" %}
|
||||
{% block title %}
|
||||
{{ assignment.title }}
|
||||
{% endblock %}
|
||||
|
||||
{% block page_content %}
|
||||
<main class="common-page-style">
|
||||
{{ Header() }}
|
||||
<div class="container form-width">
|
||||
{{ SubmissionForm(assignment) }}
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
||||
{% macro Header() %}
|
||||
<header class="sticky mb-5">
|
||||
<div class="container form-width">
|
||||
<div class="edit-header">
|
||||
<div>
|
||||
<div class="vertically-center">
|
||||
<div class="page-title">
|
||||
{{ assignment.title }}
|
||||
</div>
|
||||
{% if submission.status %}
|
||||
{% set color = "green" if submission.status == "Pass" else "red" if submission.status == "Fail" else "orange" %}
|
||||
<div class="indicator-pill {{ color }} ml-2">
|
||||
{{ submission.status }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="vertically-center small">
|
||||
<a class="dark-links" href="/classes">
|
||||
{{ _("All Classes") }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="align-self-center">
|
||||
<button class="btn btn-primary btn-sm btn-save-assignment" {% if assignment.name %} data-assignment="{{ assignment.name }}" {% endif %}
|
||||
{% if submission.name %} data-submission="{{ submission.name }}" {% endif %}>
|
||||
{{ _("Save") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro SubmissionForm(assignment) %}
|
||||
<article class="field-parent">
|
||||
{% if submission.name and is_moderator %}
|
||||
<div class="field-group">
|
||||
<div class="field-label">
|
||||
{{ _("Student Name") }}
|
||||
</div>
|
||||
{{ submission.member_name }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="field-group">
|
||||
<div class="field-label">
|
||||
{{ _("Question")}}
|
||||
</div>
|
||||
{{ assignment.question }}
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<div class="field-label">
|
||||
{{ _("Submit")}}
|
||||
</div>
|
||||
<div class="field-description">
|
||||
{{ _("Upload assignment as {0}").format(assignment.type) }}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="btn btn-default btn-sm btn-upload mt-2 {% if submission.assignment_attachment %} hide {% endif %}" data-type="{{ assignment.type }}">
|
||||
{{ _("Browse").format(assignment.type) }}
|
||||
</div>
|
||||
<div class="field-input flex justify-between align-center {% if not submission.assignment_attachment %} hide {% endif %}" id="assignment-preview">
|
||||
<a class="clickable" {% if submission.assignment_attachment %} href="{{ submission.assignment_attachment }}" {% endif %}>
|
||||
{% if submission.assignment_attachment %} {{ submission.assignment_attachment }} {% endif %}
|
||||
</a>
|
||||
<span class="btn btn-default btn-sm btn-close {% if not submission %} hide {% endif %}">
|
||||
{{ _("Clear") }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if is_moderator %}
|
||||
<div class="field-group">
|
||||
<div class="field-label">
|
||||
{{ _("Status") }}
|
||||
</div>
|
||||
<div class="field-input flex align-center">
|
||||
<select class="form-control" id="status">
|
||||
{% set statuses = ["Not Graded", "Pass", "Fail"] %}
|
||||
{% for status in statuses %}
|
||||
<option value="{{ status }}" {% if submission.status == status %} selected {% endif %}>
|
||||
{{ status }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div class="select-icon">
|
||||
<svg class="icon icon-sm">
|
||||
<use class="" href="#icon-select"></use>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<div class="field-label">
|
||||
{{ _("Comments by Mentor") }}
|
||||
</div>
|
||||
<textarea id="comments" type="text" class="field-input" height="300px"
|
||||
>{% if submission.comments %}{{ submission.comments }}{% endif %}</textarea>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if submission and submission.member == frappe.session.user and submission.comments %}
|
||||
<div class="field-group">
|
||||
<div class="field-label">
|
||||
{{ _("Comments by Mentor") }}
|
||||
</div>
|
||||
<div>
|
||||
{{ submission.comments }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</article>
|
||||
{% endmacro %}
|
||||
86
lms/www/assignment_submission/assignment_submission.js
Normal file
86
lms/www/assignment_submission/assignment_submission.js
Normal file
@@ -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");
|
||||
};
|
||||
29
lms/www/assignment_submission/assignment_submission.py
Normal file
29
lms/www/assignment_submission/assignment_submission.py
Normal file
@@ -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"))
|
||||
@@ -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 %}
|
||||
<div class="common-page-style">
|
||||
<div class="container">
|
||||
<div class="common-card-style column-card medium">
|
||||
<div class="course-home-headings"> {{ _("Assignment Grading") }} </div>
|
||||
{{ Header() }}
|
||||
<div class="container form-width">
|
||||
{{ AssignmentForm(assignment) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% macro Header() %}
|
||||
<header class="sticky">
|
||||
<div class="container form-width">
|
||||
<div class="edit-header">
|
||||
<div>
|
||||
<div class="page-title">
|
||||
{{ _("Assignment Details") }}
|
||||
</div>
|
||||
<div class="vertically-center small">
|
||||
<a class="dark-links" href="/assignments">
|
||||
{{ _("Assignment List") }}
|
||||
</a>
|
||||
<img class="icon icon-sm mr-0" src="/assets/lms/icons/chevron-right.svg">
|
||||
<span class="breadcrumb-destination">{{ assignment.title if assignment.title else _("New Assignment") }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="align-self-center">
|
||||
<button class="btn btn-primary btn-sm btn-save-assignment" {% if assignment.name %} data-assignment="{{ assignment.name }}" {% endif %}>
|
||||
{{ _("Save") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
{% 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") %}
|
||||
|
||||
<form class="register-form">
|
||||
|
||||
<div class="form-padding">
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-6">
|
||||
<label> {{ _("Member") }} </label>
|
||||
<input type="text" class="form-control" value="{{ assignment.member_name }}" disabled readonly />
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label> {{ _("Course") }} </label>
|
||||
<input type="text" class="form-control" value="{{ course_title }}" disabled readonly />
|
||||
<article>
|
||||
<div class="field-parent">
|
||||
<div class="field-group">
|
||||
<div class="field-label reqd"> {{ _("Title") }} </div>
|
||||
<div class="field-description">
|
||||
{{ _("Give the assignment a title.") }}
|
||||
</div>
|
||||
<input type="text" id="title" class="field-input" {% if assignment.name %} value="{{ assignment.title }}" data-name="{{ assignment.name }}" {% endif %}>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label> {{ _("Lesson") }} </label>
|
||||
<input type="text" class="form-control" value="{{ lesson_title }}" disabled readonly />
|
||||
<div class="field-group">
|
||||
<div class="field-label reqd"> {{ "Type" }} </div>
|
||||
<div class="field-description">
|
||||
{{ _("Select the format in which students will have to submit the assignment.") }}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label> {{ _("Result") }} </label>
|
||||
<div class="control-input flex align-center form-control">
|
||||
<select class="input-with-feedback form-control pl-0" id="result"
|
||||
{% if not is_moderator %} disabled {% endif %} data-type="{{ assignment.status }}">
|
||||
<option selected> {{ _("Not Graded") }} </option>
|
||||
<option value="Pass"> {{ _("Pass") }} </option>
|
||||
<option value="Fail"> {{ _("Fail") }} </option>
|
||||
<div class="field-input flex align-center">
|
||||
<select class="form-control" id="type">
|
||||
{% set types = ["Document", "PDF", "Image"] %}
|
||||
{% for type in types %}
|
||||
<option value="{{ type }}" {% if assignment.type == type %} selected {% endif %}>
|
||||
{{ type }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div class="select-icon">
|
||||
<svg class="icon icon-sm">
|
||||
<svg class="icon icon-sm" style="">
|
||||
<use class="" href="#icon-select"></use>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-6">
|
||||
<label>{{ _("Comments") }}</label>
|
||||
<textarea class="form-control" id="comments" {% if not is_moderator %} disabled readonly {% endif %}
|
||||
>{% if assignment.comments %}{{ assignment.comments }}{% endif %}</textarea>
|
||||
<div class="field-group">
|
||||
<div class="field-label reqd">
|
||||
{{ _("Question") }}
|
||||
</div>
|
||||
<div class="field-description">
|
||||
{{ _("Enter an assignment question.") }}
|
||||
</div>
|
||||
<div id="question" class=""></div>
|
||||
{% if assignment.question %}
|
||||
<div id="question-data" class="hide">
|
||||
{{ assignment.question }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-right form-padding">
|
||||
<a class="btn btn-secondary btn-sm" href="{{ assignment.assignment }}" target="_blank">
|
||||
{{ _("Open Attachment") }}
|
||||
</a>
|
||||
<button class="btn btn-primary btn-sm ml-2" id="save-assignment" data-assignment="{{ assignment.name }}">
|
||||
{{ _("Save") }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</article>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{%- block script %}
|
||||
{{ super() }}
|
||||
{{ include_script('controls.bundle.js') }}
|
||||
{% endblock %}
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
64
lms/www/assignments/index.html
Normal file
64
lms/www/assignments/index.html
Normal file
@@ -0,0 +1,64 @@
|
||||
{% extends "templates/base.html" %}
|
||||
{% block title %}
|
||||
{{ _("Assignment List") }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<main class="common-page-style">
|
||||
<div class="container form-width">
|
||||
{{ Header() }}
|
||||
{% if assignments | length %}
|
||||
{{ AssignmentList(assignments) }}
|
||||
{% else %}
|
||||
{{ EmptyState() }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
||||
{% macro Header() %}
|
||||
<header class="sticky">
|
||||
<div class="edit-header">
|
||||
<div class="page-title">
|
||||
{{ _("Assignment List") }}
|
||||
</div>
|
||||
|
||||
<a class="btn btn-primary btn-sm align-self-center" href="/assignments/new-assignments">
|
||||
{{ _("Add Assignment") }}
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro AssignmentList(assignments) %}
|
||||
<div class="mt-5">
|
||||
<ul class="list-unstyled">
|
||||
{% for assignment in assignments %}
|
||||
<li class="list-row">
|
||||
<a class="clickable" href="/assignments/{{ assignment.name }}">
|
||||
<span>
|
||||
{{ loop.index }}.
|
||||
</span>
|
||||
<span>
|
||||
{{ assignment.title }}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro EmptyState() %}
|
||||
<div class="empty-state mt-5">
|
||||
<img class="icon icon-xl" src="/assets/lms/icons/comment.svg">
|
||||
<div class="empty-state-text">
|
||||
<div class="empty-state-heading">
|
||||
{{ _("You have not created any assignment yet.") }}
|
||||
</div>
|
||||
<div class="course-meta ">
|
||||
{{ _("Create an assignment and evaluate your students.") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
10
lms/www/assignments/index.py
Normal file
10
lms/www/assignments/index.py
Normal file
@@ -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"],
|
||||
)
|
||||
@@ -6,7 +6,7 @@ frappe.ready(() => {
|
||||
}
|
||||
|
||||
setup_editor();
|
||||
fetch_quiz_list();
|
||||
//fetch_quiz_list();
|
||||
|
||||
$("#save-lesson").click((e) => {
|
||||
save_lesson(e);
|
||||
|
||||
@@ -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"),
|
||||
},
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -35,9 +35,14 @@
|
||||
<div class="mt-5">
|
||||
<ul class="list-unstyled">
|
||||
{% for quiz in quiz_list %}
|
||||
<li class="mt-2">
|
||||
<li class="outline-lesson">
|
||||
<a class="clickable" href="/quizzes/{{ quiz.name }}">
|
||||
<span>
|
||||
{{ loop.index }}.
|
||||
</span>
|
||||
<span>
|
||||
{{ quiz.title }}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
@@ -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"]
|
||||
)
|
||||
|
||||
@@ -87,10 +87,24 @@
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{% if is_moderator %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#assessments">
|
||||
{{ _("Assessments") }}
|
||||
<span class="course-list-count">
|
||||
{{ assessments | length }}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if class_students | length and (is_moderator or is_student) %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#live-class">
|
||||
{{ _("Live Class") }}
|
||||
<span class="course-list-count">
|
||||
{{ live_classes | length }}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
@@ -108,6 +122,12 @@
|
||||
{{ StudentsSection(class_info, class_students) }}
|
||||
</div>
|
||||
|
||||
{% if is_moderator %}
|
||||
<div class="tab-pane" id="assessments" role="tabpanel" aria-labelledby="assessments">
|
||||
{{ AssessmentsSection(class_info) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if class_students | length and (is_moderator or is_student) %}
|
||||
<div class="tab-pane" id="live-class" role="tabpanel" aria-labelledby="live-class">
|
||||
{{ LiveClassSection(class_info, live_classes) }}
|
||||
@@ -138,9 +158,9 @@
|
||||
<div>
|
||||
{% for course in class_courses %}
|
||||
<div class="list-row level">
|
||||
<div {% if is_moderator %} class="clickable" {% endif %}>
|
||||
<a class="clickable" href="/courses/{{ course.course }}">
|
||||
{{ course.title }}
|
||||
</div>
|
||||
</a>
|
||||
{% if is_moderator %}
|
||||
<div type="button" class="btn-remove-course" data-course="{{ course.course }}">
|
||||
<svg class="icon icon-sm">
|
||||
@@ -159,7 +179,6 @@
|
||||
|
||||
|
||||
</article>
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
@@ -182,8 +201,7 @@
|
||||
<div>
|
||||
{% 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 %}
|
||||
|
||||
<div class="list-row level">
|
||||
<div>
|
||||
@@ -209,6 +227,127 @@
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro AssessmentsSection(class_info) %}
|
||||
<article>
|
||||
<header class="edit-header mb-5">
|
||||
<div class="bold-heading">
|
||||
{{ _("Assessments") }}
|
||||
</div>
|
||||
{% if is_moderator %}
|
||||
<button class="btn btn-default btn-sm" id="open-assessment-modal">
|
||||
{{ _("Manage Assessments") }}
|
||||
</button>
|
||||
{% endif %}
|
||||
</header>
|
||||
{{ CreateAssessment() }}
|
||||
{{ AssessmentList(assessments) }}
|
||||
</article>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro CreateAssessment() %}
|
||||
{% if is_moderator %}
|
||||
<div class="modal fade assessment-modal" id="assessment-modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<div class="modal-title">{{ _("Manage Assessments") }}</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div class="mb-5">
|
||||
<div class="field-label">
|
||||
{{ _("Create New") }}
|
||||
</div>
|
||||
<p class="field-description">
|
||||
{{ _("To create a new assignment for this class, click on the create assignment button. Once you have created the new assignment you can come back to the class and add the assignment from here.") }}
|
||||
</p>
|
||||
<div>
|
||||
<a class="btn btn-default btn-sm" href="/assignments/new-assignment">
|
||||
{{ _("Create Assignment") }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form class="" id="assessment-form">
|
||||
{% if all_assignments | length %}
|
||||
<div>
|
||||
<div class="field-label mb-2">
|
||||
{{ _("Assignments") }}
|
||||
</div>
|
||||
<p class="field-description">
|
||||
{{ _("Select the assignments you wish to include for the assessment of this class. Your selections will be automatically saved upon clicking. If you decide to remove an item from the list, simply uncheck it.") }}
|
||||
</p>
|
||||
{% for assignment in all_assignments %}
|
||||
<div>
|
||||
<label class="vertically-center">
|
||||
<input type="checkbox" class="assessment-item" {% if assignment.checked %} checked {% endif %} value="{{ assignment.name }}" data-type="LMS Assignment" data-name="{{ assignment.name }}">
|
||||
{{ assignment.title }}
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<!-- {% if all_quizzes | length %}
|
||||
<div>
|
||||
<div class="field-label mb-2">
|
||||
{{ _("Quiz") }}
|
||||
</div>
|
||||
{% for quiz in all_quizzes %}
|
||||
<div>
|
||||
<label class="vertically-center">
|
||||
<input type="checkbox" class="assessment-item" {% if quiz.checked %} checked {% endif %} value="{{ quiz.name }}" data-type="LMS Quiz" data-name="{{ quiz.name }}">
|
||||
{{ quiz.title }}
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %} -->
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary btn-sm btn-assessment-close" data-dismiss="modal" aria-label="Close">
|
||||
{{ _("Done") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro AssessmentList(assessments) %}
|
||||
{% if assessments | length %}
|
||||
<div>
|
||||
<div class="list-row level level-left small">
|
||||
<div class="w-25">
|
||||
{{ _("Title") }}
|
||||
</div>
|
||||
<div class="">
|
||||
{{ _("Type") }}
|
||||
</div>
|
||||
</div>
|
||||
{% for assessment in assessments %}
|
||||
<div class="list-row level level-left">
|
||||
<div class="w-25">
|
||||
<a class="clickable" href="{{ assessment.edit_url }}">
|
||||
{{ assessment.title }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="">
|
||||
{{ assessment.assessment_type.split("LMS ")[1] }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-muted mt-3"> {{ _("No Assessments") }} </p>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro LiveClassSection(class_info, live_classes) %}
|
||||
<article>
|
||||
<header class="edit-header">
|
||||
@@ -230,7 +369,6 @@
|
||||
{% macro CreateLiveClass(class_info) %}
|
||||
|
||||
{% if is_moderator %}
|
||||
|
||||
<div class="modal fade live-class-modal" id="live-class-modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
frappe.ready(() => {
|
||||
let self = this;
|
||||
|
||||
$(".btn-add-student").click((e) => {
|
||||
show_student_modal(e);
|
||||
});
|
||||
@@ -27,6 +29,19 @@ frappe.ready(() => {
|
||||
$(".btn-remove-course").click((e) => {
|
||||
remove_course(e);
|
||||
});
|
||||
|
||||
$("#open-assessment-modal").click((e) => {
|
||||
e.preventDefault();
|
||||
$("#assessment-modal").modal("show");
|
||||
});
|
||||
|
||||
$(".assessment-item").click((e) => {
|
||||
update_assessment(e);
|
||||
});
|
||||
|
||||
$(".btn-assessment-close").click((e) => {
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
||||
|
||||
const submit_student = (e) => {
|
||||
@@ -453,3 +468,15 @@ const show_student_modal = () => {
|
||||
$(".modal-body input").focus();
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const update_assessment = (e) => {
|
||||
frappe.call({
|
||||
method: "lms.lms.doctype.lms_class.lms_class.update_assessment",
|
||||
args: {
|
||||
type: $(e.currentTarget).data("type"),
|
||||
name: $(e.currentTarget).data("name"),
|
||||
value: $(e.currentTarget).prop("checked") ? 1 : 0,
|
||||
class_name: $(".class-details").data("class"),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import frappe
|
||||
from lms.lms.utils import has_course_moderator_role
|
||||
from frappe import _
|
||||
from frappe.utils import getdate
|
||||
from lms.www.utils import get_assessments
|
||||
|
||||
|
||||
def get_context(context):
|
||||
@@ -9,6 +10,7 @@ def get_context(context):
|
||||
class_name = frappe.form_dict["classname"]
|
||||
session_user = []
|
||||
remaining_students = []
|
||||
context.is_moderator = has_course_moderator_role()
|
||||
|
||||
context.class_info = frappe.db.get_value(
|
||||
"LMS Class",
|
||||
@@ -56,8 +58,6 @@ def get_context(context):
|
||||
else:
|
||||
context.class_students = class_students
|
||||
|
||||
context.is_moderator = has_course_moderator_role()
|
||||
|
||||
students = [student.student for student in class_students]
|
||||
context.is_student = frappe.session.user in students
|
||||
|
||||
@@ -67,3 +67,39 @@ def get_context(context):
|
||||
["title", "description", "time", "date", "start_url", "join_url", "owner"],
|
||||
order_by="date",
|
||||
)
|
||||
|
||||
context.assessments = get_assessments(class_name)
|
||||
context.all_assignments = get_all_assignments(class_name)
|
||||
context.all_quizzes = get_all_quizzes(class_name)
|
||||
|
||||
|
||||
def get_all_quizzes(class_name):
|
||||
all_quizzes = frappe.get_all(
|
||||
"LMS Quiz", {"owner": frappe.session.user}, ["name", "title"]
|
||||
)
|
||||
for quiz in all_quizzes:
|
||||
quiz.checked = frappe.db.exists(
|
||||
{
|
||||
"doctype": "LMS Assessment",
|
||||
"assessment_type": "LMS Quiz",
|
||||
"assessment_name": quiz.name,
|
||||
"parent": class_name,
|
||||
}
|
||||
)
|
||||
return all_quizzes
|
||||
|
||||
|
||||
def get_all_assignments(class_name):
|
||||
all_assignments = frappe.get_all(
|
||||
"LMS Assignment", {"owner": frappe.session.user}, ["name", "title"]
|
||||
)
|
||||
for assignment in all_assignments:
|
||||
assignment.checked = frappe.db.exists(
|
||||
{
|
||||
"doctype": "LMS Assessment",
|
||||
"assessment_type": "LMS Assignment",
|
||||
"assessment_name": assignment.name,
|
||||
"parent": class_name,
|
||||
}
|
||||
)
|
||||
return all_assignments
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<div class="container">
|
||||
{{ Header() }}
|
||||
{% if past_classes | length or upcoming_classes | length %}
|
||||
{{ ClassTabs(past_classes, upcoming_classes) }}
|
||||
{{ ClassTabs(past_classes, upcoming_classes, my_classes) }}
|
||||
{% else %}
|
||||
{{ EmptyState() }}
|
||||
{% endif %}
|
||||
@@ -27,7 +27,7 @@
|
||||
</header>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro ClassTabs(past_classes, upcoming_classes) %}
|
||||
{% macro ClassTabs(past_classes, upcoming_classes, my_classes) %}
|
||||
<article>
|
||||
<ul class="nav lms-nav" id="courses-tab">
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{% if is_moderator %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#past">
|
||||
{{ _("Past Classes") }}
|
||||
@@ -48,6 +49,18 @@
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if frappe.session.user != "Guest" %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#my-class">
|
||||
{{ _("My Classes") }}
|
||||
<span class="course-list-count">
|
||||
{{ my_classes | length }}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
</ul>
|
||||
|
||||
@@ -58,9 +71,17 @@
|
||||
{{ ClassCards(upcoming_classes) }}
|
||||
</div>
|
||||
|
||||
{% if is_moderator %}
|
||||
<div class="tab-pane" id="past" role="tabpanel" aria-labelledby="past">
|
||||
{{ ClassCards(past_classes) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if frappe.session.user != "Guest" %}
|
||||
<div class="tab-pane" id="my-class" role="tabpanel" aria-labelledby="my-classes">
|
||||
{{ ClassCards(my_classes) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</article>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -6,179 +6,74 @@
|
||||
|
||||
{% block page_content %}
|
||||
<div class="common-page-style">
|
||||
<div class="container">
|
||||
{{ BreadCrumb(class_info, student) }}
|
||||
<div class="common-card-style flex-column p-5">
|
||||
<div class="mb-5">
|
||||
<div class="medium d-flex align-items-center pull-right">
|
||||
<span>
|
||||
{{ frappe.utils.format_datetime(student.last_active, "medium") }}
|
||||
</span>
|
||||
{% if is_moderator %}
|
||||
<a class="btn btn-secondary btn-sm ml-3" href="/evaluation/new?member={{student.name}}&date={{frappe.utils.getdate()}}&class={{class_info.name}}">
|
||||
{{ _("Evaluate") }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<a class="course-home-headings" href="/users/{{student.username}}">
|
||||
{{ student.full_name }}
|
||||
</a>
|
||||
</div>
|
||||
{{ Progress(class_courses, student) }}
|
||||
</div>
|
||||
{{ Header() }}
|
||||
<div class="container form-width">
|
||||
{{ Progress(class_info, student) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% macro BreadCrumb(class_info, student) %}
|
||||
<div class="breadcrumb">
|
||||
<a class="dark-links" href="/classes">{{ _("All Classes") }}</a>
|
||||
<img class="ml-1 mr-1" src="/assets/lms/icons/chevron-right.svg">
|
||||
<a class="dark-links" href="/classes/{{ class_info.name }}">{{ class_info.name }}</a>
|
||||
<img class="ml-1 mr-1" src="/assets/lms/icons/chevron-right.svg">
|
||||
<span class="breadcrumb-destination">{{ student.full_name }}</span>
|
||||
{% macro Header() %}
|
||||
<header class="sticky mb-5">
|
||||
<div class="container form-width">
|
||||
<div class="edit-header">
|
||||
<div>
|
||||
<div class="page-title">
|
||||
{{ _("{0}'s Progress").format(student.full_name) }}
|
||||
</div>
|
||||
<div class="vertically-center small">
|
||||
<a class="dark-links" href="/classes">
|
||||
{{ _("All Classes") }}
|
||||
</a>
|
||||
<img class="icon icon-sm mr-0" src="/assets/lms/icons/chevron-right.svg">
|
||||
<a class="dark-links" href="/classes/{{ class_info.name }}">
|
||||
{{ class_info.name }}
|
||||
</a>
|
||||
<img class="icon icon-sm mr-0" src="/assets/lms/icons/chevron-right.svg">
|
||||
<span class="breadcrumb-destination">{{ _("{0}'s Progress").format(student.full_name) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if is_moderator %}
|
||||
<div class="align-self-center">
|
||||
<a class="btn btn-primary btn-sm btn-evaluate" href=/evaluation/new?member={{student.name}}&date={{frappe.utils.getdate()}}&class={{class_info.name}}">
|
||||
{{ _("Evaluate") }}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro Progress(class_info, student) %}
|
||||
<div>
|
||||
{% for course in class_courses %}
|
||||
{% set progress = course.membership.progress %}
|
||||
<div class="medium">
|
||||
<div class="progress-course-header">
|
||||
<div class="section-heading"> {{ course.title }} </div>
|
||||
<div class="ml-3"> {{ frappe.utils.cint(course.membership.progress) }}% </div>
|
||||
</div>
|
||||
{% if assessments | length %}
|
||||
<article>
|
||||
{% for assessment in assessments %}
|
||||
<div class="list-row level">
|
||||
<a {% if is_moderator and assessment.submission or frappe.session.user == student.name %} class="clickable" href="{{ assessment.url }}" {% endif %}>
|
||||
{{ assessment.title }}
|
||||
</a>
|
||||
|
||||
{% if course.quizzes | length or course.assignments | length or course.evaluations | length %}
|
||||
<div class="{% if not loop.last %} my-5 {% else %} mt-5 {% endif %}">
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th style="width: 20%;">
|
||||
{{ _("Type") }}
|
||||
</th>
|
||||
<th style="width: 40%;">
|
||||
{{ _("Title") }}
|
||||
</th>
|
||||
<th style="width: 20%;">
|
||||
{{ _("Score/Status") }}
|
||||
</th>
|
||||
<th style="width: 20%;">
|
||||
{{ _("Last Attempt Date") }}
|
||||
</th>
|
||||
</tr>
|
||||
|
||||
{{ Quiz(course, student) }}
|
||||
{{ Assignment(course, student, is_moderator) }}
|
||||
{{ Evaluation(course, student, is_moderator) }}
|
||||
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-muted medium my-5">
|
||||
{{ _("There are no activities in this course.") }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% 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}) %}
|
||||
|
||||
<tr>
|
||||
<td class="vertically-center">
|
||||
<svg class="icon icon-sm">
|
||||
<use href="#icon-quiz"></use>
|
||||
</svg>
|
||||
{{ _("Quiz") }}
|
||||
</td>
|
||||
<td>{{ quiz.title }}</td>
|
||||
{% if has_submitted %}
|
||||
<td>{{ submission.score }}/{{ total_questions }}</td>
|
||||
<td>{{ frappe.utils.format_date(submission.creation, "medium") }}</td>
|
||||
{% else %}
|
||||
<td>-</td>
|
||||
<td>
|
||||
<div class="indicator-pill red">
|
||||
{{ _("Not Attempted") }}
|
||||
</div>
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% 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 %}
|
||||
{% if assessment.submission %}
|
||||
{% set status = assessment.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) %}
|
||||
|
||||
<tr {% if can_see_details %} class="clickable-row" data-href="/assignments/{{ has_submitted }}" {% endif %}>
|
||||
<td class="{% if can_see_details %} subheading {% endif %} vertically-center">
|
||||
<svg class="icon icon-md">
|
||||
<use href="#icon-file"></use>
|
||||
</svg>
|
||||
{{ _("Assignment") }}
|
||||
</td>
|
||||
|
||||
<td>{{ assignment.title }}</td>
|
||||
|
||||
{% if has_submitted %}
|
||||
<td>
|
||||
<div>
|
||||
<div class="indicator-pill {{ color }}">
|
||||
{{ status }}
|
||||
{{ assessment.submission.status }}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td>{{ frappe.utils.format_date(submission.creation, "medium") }}</td>
|
||||
|
||||
{% else %}
|
||||
<td>-</td>
|
||||
|
||||
<td>
|
||||
<div class="indicator-pill red">
|
||||
{{ _("Not Attempted") }}
|
||||
</div>
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% 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 %}
|
||||
|
||||
<tr {% if can_see_details %} class="clickable-row" data-href="/evaluation/{{evaluation.name}}" {% endif %}>
|
||||
<td class="{% if can_see_details %} subheading {% endif %} vertically-center">
|
||||
<svg class="icon icon-md">
|
||||
<use href="#icon-quality"></use>
|
||||
</svg>
|
||||
{{ _("Evaluation") }}
|
||||
</td>
|
||||
<td> - </td>
|
||||
<td>
|
||||
<div class="indicator-pill {{ color }}">
|
||||
{{ evaluation.status }}
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ frappe.utils.format_date(evaluation.creation, "medium") }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</article>
|
||||
{% else %}
|
||||
<p class="text-muted mt-3"> {{ _("No Assessments") }} </p>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
@@ -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)
|
||||
|
||||
@@ -57,3 +57,49 @@ def get_current_lesson_details(lesson_number, context, is_edit=False):
|
||||
lesson_info = details_list[0]
|
||||
lesson_info.body = lesson_info.body.replace('"', "'")
|
||||
return lesson_info
|
||||
|
||||
|
||||
def get_assessments(class_name, member=None):
|
||||
if not member:
|
||||
member = frappe.session.user
|
||||
|
||||
assessments = frappe.get_all(
|
||||
"LMS Assessment",
|
||||
{"parent": class_name},
|
||||
["name", "assessment_type", "assessment_name"],
|
||||
)
|
||||
|
||||
for assessment in assessments:
|
||||
if assessment.assessment_type == "LMS Assignment":
|
||||
assessment.title = frappe.db.get_value(
|
||||
"LMS Assignment", assessment.assessment_name, "title"
|
||||
)
|
||||
|
||||
existing_submission = frappe.db.exists(
|
||||
{
|
||||
"doctype": "LMS Assignment Submission",
|
||||
"member": member,
|
||||
"assignment": assessment.assessment_name,
|
||||
}
|
||||
)
|
||||
|
||||
if existing_submission:
|
||||
assessment.submission = frappe.db.get_value(
|
||||
"LMS Assignment Submission",
|
||||
existing_submission,
|
||||
["name", "status", "comments"],
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
assessment.edit_url = f"/assignments/{assessment.assessment_name}"
|
||||
submission_name = existing_submission if existing_submission else "new-submission"
|
||||
assessment.url = (
|
||||
f"/assignment-submission/{assessment.assessment_name}/{submission_name}"
|
||||
)
|
||||
|
||||
elif assessment.assessment_type == "LMS Quiz":
|
||||
assessment.title = frappe.db.get_value(
|
||||
"LMS Quiz", assessment.assessment_name, "title"
|
||||
)
|
||||
assessment.url = f"/quizzes/{assessment.assessment_name}"
|
||||
return assessments
|
||||
|
||||
Reference in New Issue
Block a user