feat: assessment tab in class

This commit is contained in:
Jannat Patel
2023-05-30 22:11:14 +05:30
parent 70a036e5a7
commit bb39999b84
41 changed files with 1157 additions and 263 deletions

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"python.formatting.provider": "black"
}

View File

@@ -179,7 +179,15 @@ website_route_rules = [
"from_route": "/classes/<classname>/students/<username>", "from_route": "/classes/<classname>/students/<username>",
"to_route": "/classes/progress", "to_route": "/classes/progress",
}, },
{
"from_route": "/assignment-grading/<assignment>",
"to_route": "assignment_grading/assignment_grading",
},
{"from_route": "/assignments/<assignment>", "to_route": "assignments/assignment"}, {"from_route": "/assignments/<assignment>", "to_route": "assignments/assignment"},
{
"from_route": "/assignment-submission/<assignment>/<submission>",
"to_route": "assignment_submission/assignment_submission",
},
] ]
website_redirects = [ website_redirects = [

View 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": []
}

View 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

View 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) {
// },
// });

View 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": []
}

View File

@@ -0,0 +1,26 @@
# Copyright (c) 2023, Frappe and contributors
# For license information, please see license.txt
import frappe
from frappe.model.document import Document
from lms.lms.utils import can_create_courses
class LMSAssignment(Document):
pass
@frappe.whitelist()
def save_assignment(assignment, title, type, question):
if not can_create_courses():
return
if assignment:
doc = frappe.get_doc("LMS Assignment", assignment)
else:
doc = frappe.get_doc({"doctype": "LMS Assignment"})
doc.update({"title": title, "type": type, "question": question})
doc.save(ignore_permissions=True)
return doc.name

View 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

View File

@@ -1,20 +1,29 @@
{ {
"actions": [], "actions": [],
"allow_rename": 1, "allow_rename": 1,
"autoname": "format: ASG-SUB-{#####}",
"creation": "2021-12-21 16:15:22.651658", "creation": "2021-12-21 16:15:22.651658",
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"assignment", "assignment",
"lesson", "assignment_title",
"course", "question",
"evaluator",
"status",
"column_break_3", "column_break_3",
"member", "member",
"member_name", "member_name",
"comments" "type",
"assignment_attachment",
"section_break_rqal",
"status",
"column_break_esgd",
"comments",
"section_break_cwaw",
"lesson",
"course",
"column_break_ygdu",
"evaluator"
], ],
"fields": [ "fields": [
{ {
@@ -23,8 +32,7 @@
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Lesson", "label": "Lesson",
"options": "Course Lesson", "options": "Course Lesson"
"reqd": 1
}, },
{ {
"fieldname": "column_break_3", "fieldname": "column_break_3",
@@ -32,8 +40,9 @@
}, },
{ {
"fieldname": "assignment", "fieldname": "assignment",
"fieldtype": "Attach", "fieldtype": "Link",
"label": "Assignment", "label": "Assignment",
"options": "LMS Assignment",
"reqd": 1 "reqd": 1
}, },
{ {
@@ -77,21 +86,65 @@
"label": "Comments" "label": "Comments"
}, },
{ {
"fetch_from": "course.evaluator", "fetch_from": "course.",
"fieldname": "evaluator", "fieldname": "evaluator",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Evaluator", "label": "Evaluator",
"options": "User", "options": "User",
"read_only": 1 "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, "index_web_pages_for_search": 1,
"links": [], "links": [],
"make_attachments_public": 1, "make_attachments_public": 1,
"modified": "2023-03-27 13:24:18.696869", "modified": "2023-05-30 16:10:09.173258",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "LMS", "module": "LMS",
"name": "LMS Assignment Submission", "name": "LMS Assignment Submission",
"naming_rule": "Expression (old style)",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
@@ -123,5 +176,5 @@
"title": "Fail" "title": "Fail"
} }
], ],
"title_field": "lesson" "title_field": "assignment_title"
} }

View File

@@ -6,14 +6,14 @@ from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
class LessonAssignment(Document): class LMSAssignmentSubmission(Document):
def validate(self): def validate(self):
self.validate_duplicates() self.validate_duplicates()
def validate_duplicates(self): def validate_duplicates(self):
if frappe.db.exists( if frappe.db.exists(
"LMS Assignment Submission", "LMS Assignment Submission",
{"lesson": self.lesson, "member": self.member, "name": ["!=", self.name]}, {"assignment": self.assignment, "member": self.member, "name": ["!=", self.name]},
): ):
lesson_title = frappe.db.get_value("Course Lesson", self.lesson, "title") lesson_title = frappe.db.get_value("Course Lesson", self.lesson, "title")
frappe.throw( frappe.throw(
@@ -24,19 +24,26 @@ class LessonAssignment(Document):
@frappe.whitelist() @frappe.whitelist()
def upload_assignment(assignment, lesson): def upload_assignment(assignment_attachment, assignment, lesson=None):
if frappe.session.user == "Guest":
return
args = { args = {
"doctype": "LMS Assignment Submission", "doctype": "LMS Assignment Submission",
"lesson": lesson,
"member": frappe.session.user, "member": frappe.session.user,
"assignment": assignment,
} }
if frappe.db.exists(args): if frappe.db.exists(args):
del args["doctype"] del args["doctype"]
frappe.db.set_value("LMS Assignment Submission", args, "assignment", assignment) frappe.db.set_value(
"LMS Assignment Submission", args, "assignment_attachment", assignment_attachment
)
return frappe.db.get_value("LMS Assignment Submission", args, "name")
else: else:
args.update({"assignment": assignment}) args.update({"assignment_attachment": assignment_attachment})
lesson_work = frappe.get_doc(args) doc = frappe.get_doc(args)
lesson_work.save(ignore_permissions=True) doc.save(ignore_permissions=True)
return doc.name
@frappe.whitelist() @frappe.whitelist()

View File

@@ -5,5 +5,5 @@
import unittest import unittest
class TestLessonAssignment(unittest.TestCase): class TestLMSAssignmentSubmission(unittest.TestCase):
pass pass

View File

@@ -20,7 +20,9 @@
"description", "description",
"students", "students",
"courses", "courses",
"custom_component" "custom_component",
"assessment_tab",
"assessment"
], ],
"fields": [ "fields": [
{ {
@@ -96,11 +98,22 @@
"fieldname": "end_time", "fieldname": "end_time",
"fieldtype": "Time", "fieldtype": "Time",
"label": "End 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, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2023-05-03 23:07:06.725720", "modified": "2023-05-29 14:52:45.866901",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "LMS", "module": "LMS",
"name": "LMS Class", "name": "LMS Class",

View File

@@ -180,3 +180,22 @@ def create_class(
) )
class_details.save() class_details.save()
return class_details return class_details
@frappe.whitelist()
def update_assessment(type, name, value, class_name):
value = cint(value)
filters = {
"assessment_type": type,
"assessment_name": name,
"parent": class_name,
"parenttype": "LMS Class",
"parentfield": "assessment",
}
exists = frappe.db.exists("LMS Assessment", filters)
if exists and not value:
frappe.db.delete("LMS Assessment", exists)
elif not exists and value:
filters.update({"doctype": "LMS Assessment"})
frappe.get_doc(filters).insert()

View File

@@ -7,7 +7,7 @@ import frappe
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import cint from frappe.utils import cint
from frappe.utils.telemetry import capture 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 from ...utils import generate_slug, validate_image
@@ -212,6 +212,9 @@ def save_course(
upcoming, upcoming,
image=None, image=None,
): ):
if not can_create_courses():
return
if course: if course:
doc = frappe.get_doc("LMS Course", course) doc = frappe.get_doc("LMS Course", course)
else: else:

View File

@@ -49,7 +49,6 @@ lms.patches.v0_0.rename_community_to_users #06-01-2023
lms.patches.v0_0.video_embed_link lms.patches.v0_0.video_embed_link
lms.patches.v0_0.rename_exercise_doctype lms.patches.v0_0.rename_exercise_doctype
lms.patches.v0_0.add_question_type #09-04-2023 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 lms.patches.v0_0.share_certificates
execute:frappe.delete_doc("Web Form", "class", ignore_missing=True, force=True) 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.amend_course_and_lesson_editor_fields

View File

@@ -2,6 +2,7 @@ import frappe
def execute(): def execute():
if frappe.db.exists("DocType", "Lesson Assignment"):
frappe.reload_doc("lms", "doctype", "lesson_assignment") frappe.reload_doc("lms", "doctype", "lesson_assignment")
assignments = frappe.get_all("Lesson Assignment", fields=["name", "course"]) assignments = frappe.get_all("Lesson Assignment", fields=["name", "course"])
for assignment in assignments: for assignment in assignments:

View File

@@ -4,6 +4,10 @@
--text-4xl: 36px; --text-4xl: 36px;
} }
body {
font-size: var(--text-base);
}
.nav-link .course-list-count { .nav-link .course-list-count {
border-radius: var(--border-radius-md); border-radius: var(--border-radius-md);
padding: 0 0.3rem; padding: 0 0.3rem;
@@ -86,6 +90,14 @@
outline: none; outline: none;
} }
.field-input .form-control {
color: initial;
background-color: inherit;
padding: 0;
height: inherit;
cursor: pointer;
}
.field-group { .field-group {
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
@@ -103,11 +115,26 @@
} }
.image-preview { .image-preview {
width: 280px; width: 100%;
height: 178px; height: 100%;
border-radius: var(--border-radius-sm); border: none;
}
.file-source-preview {
display: flex;
align-items: center;
justify-content: center;
position: relative;
width: 100%;
height: 250px;
border: 1px solid var(--gray-300); border: 1px solid var(--gray-300);
margin-top: 1rem; border-radius: var(--border-radius-md);
}
.file-source-preview .btn-close {
position: absolute;
top: 5%;
right: 5%;
} }
textarea.field-input { textarea.field-input {
@@ -119,7 +146,7 @@ textarea.field-input {
border-bottom: 1px solid var(--gray-300); border-bottom: 1px solid var(--gray-300);
} }
.common-card-style .outline-lesson:last-of-type { .outline-lesson:last-of-type {
border-bottom: none; border-bottom: none;
} }
@@ -173,6 +200,7 @@ textarea.field-input {
.clickable { .clickable {
color: var(--gray-900); color: var(--gray-900);
font-weight: 500; font-weight: 500;
cursor: pointer;
} }
.clickable:hover { .clickable:hover {
@@ -328,7 +356,6 @@ input[type=checkbox] {
.icon { .icon {
margin: 0; margin: 0;
margin-right: 0.25rem;
} }
.lesson-links .icon { .lesson-links .icon {
@@ -1876,10 +1903,6 @@ li {
padding: 0 1rem !important; padding: 0 1rem !important;
} }
.modal-content {
font-size: var(--text-sm) !important;
}
.modal-header, .modal-body { .modal-header, .modal-body {
margin-bottom: 0.5rem !important; margin-bottom: 0.5rem !important;
} }
@@ -2156,3 +2179,7 @@ select {
.list-row:last-child { .list-row:last-child {
border-bottom: none; border-bottom: none;
} }
.fill-width-twice {
flex: 2;
}

View File

View File

@@ -0,0 +1,80 @@
{% extends "templates/base.html" %}
{% block title %}
{{ _("Assignments") }}
{% endblock %}
{% block 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>
{{ AssignmentForm(assignment) }}
</div>
</div>
</div>
{% endblock %}
{% macro AssignmentForm(assignment) %}
{% set course_title = frappe.db.get_value("LMS Course", assignment.course, "title") %}
{% set lesson_title = frappe.db.get_value("Course Lesson", assignment.lesson, "title") %}
<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 />
</div>
</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>
<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>
</select>
<div class="select-icon">
<svg class="icon icon-sm">
<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>
</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>
{% endmacro %}

View File

@@ -0,0 +1,55 @@
frappe.ready(() => {
this.result;
let self = this;
set_result();
$("#save-assignment").click((e) => {
save_assignment(e);
});
$("#result").change((e) => {
$("#result option:selected").each(function () {
self.result = $(this).val();
});
});
});
const set_result = () => {
let self = this;
let result = $("#result").data("type");
if (result) {
$("#result option").each((i, elem) => {
if ($(elem).val() == result) {
$(elem).attr("selected", true);
self.result = result;
}
});
}
};
const save_assignment = (e) => {
e.preventDefault();
if (!["Pass", "Fail"].includes(this.result))
frappe.throw({
title: __("Not Graded"),
message: __("Please grade the assignment."),
});
frappe.call({
method: "lms.lms.doctype.lms_assignment_submission.lms_assignment_submission.grade_assignment",
args: {
name: $(e.currentTarget).data("assignment"),
result: this.result,
comments: $("#comments").val(),
},
callback: (data) => {
frappe.show_alert({
message: __("Saved"),
indicator: "green",
});
setTimeout(() => {
window.history.go(-2);
}, 2000);
},
});
};

View File

@@ -0,0 +1,35 @@
import frappe
from lms.lms.utils import has_course_moderator_role
from frappe import _
def get_context(context):
context.no_cache = 1
assignment = frappe.form_dict["assignment"]
context.assignment = frappe.db.get_value(
"LMS Assignment Submission",
assignment,
[
"assignment",
"comments",
"status",
"name",
"member",
"member_name",
"course",
"lesson",
],
as_dict=True,
)
context.is_moderator = has_course_moderator_role()
if (
not has_course_moderator_role()
and not frappe.session.user == context.assignment.member
):
message = "You don't have the permissions to access this page."
if frappe.session.user == "Guest":
message = "Please login to access this page."
raise frappe.PermissionError(_(message))

View File

@@ -0,0 +1,72 @@
{% 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 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>
{% if submission.status == "Not Graded" %}
<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>
{% endif %}
</div>
</div>
</header>
{% endmacro %}
{% macro SubmissionForm(assignment) %}
<article class="field-parent">
<div class="field-group">
<div class="field-label">
{{ _("Question")}}
</div>
{{ assignment.question }}
</div>
<div class="field-group">
<div class="field-label">
{{ _("Submit your assignment")}}
</div>
<div class="file-source-preview">
{% if submission.status == "Not Graded" %}
<span class="btn btn-default btn-sm btn-close">
{{ _("Clear") }}
</span>
{% endif %}
<div class="btn-upload clickable {% if submission.assignment_attachment %} hide {% endif %}" data-type="{{ assignment.type }}">
{{ _("Upload a {0}").format(assignment.type) }}
</div>
<iframe class="image-preview {% if not submission.assignment_attachment %} hide {% endif %}" {% if submission.assignment_attachment %} src="{{ submission.assignment_attachment }}" {% endif %}></iframe>
</div>
</div>
</article>
{% endmacro %}

View File

@@ -0,0 +1,74 @@
frappe.ready(() => {
$(".btn-upload").click((e) => {
upload_file(e);
});
$(".btn-save-assignment").click((e) => {
save_assignment(e);
});
$(".btn-close").click((e) => {
clear_preview(e);
});
});
const upload_file = (e) => {
let type = $(e.currentTarget).data("type");
let mapper = {
Image: "image/*",
Document:
".doc,.docx,.xml,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document",
PDF: ".pdf",
};
new frappe.ui.FileUploader({
disable_file_browser: true,
folder: "Home/Attachments",
make_attachments_public: true,
restrictions: {
allowed_file_types: [mapper[type]],
},
on_success: (file_doc) => {
$(e.currentTarget).addClass("hide");
$(".file-source-preview .btn-close").removeClass("hide");
$(".file-source-preview iframe")
.attr("src", file_doc.file_url)
.removeClass("hide");
},
});
};
const save_assignment = (e) => {
let file = $(".image-preview").attr("src");
if (!file) {
frappe.throw({
title: __("No File"),
message: __("Please upload a file."),
});
}
frappe.call({
method: "lms.lms.doctype.lms_assignment_submission.lms_assignment_submission.upload_assignment",
args: {
assignment: $(e.currentTarget).data("assignment"),
assignment_attachment: file,
},
callback: (data) => {
frappe.show_alert({
message: __("Saved"),
indicator: "green",
});
setTimeout(() => {
window.location.href = `/assignment-submission/${$(
e.currentTarget
).data("assignment")}/${data.message}`;
}, 2000);
},
});
};
const clear_preview = (e) => {
$(".file-source-preview .btn-upload").removeClass("hide");
$(".file-source-preview iframe").attr("src", "").addClass("hide");
$(".file-source-preview .btn-close").addClass("hide");
};

View File

@@ -0,0 +1,24 @@
import frappe
from frappe import _
def get_context(context):
context.no_cache = 1
submission = frappe.form_dict["submission"]
assignment = frappe.form_dict["assignment"]
context.assignment = frappe.db.get_value(
"LMS Assignment", assignment, ["title", "name", "type", "question"], as_dict=1
)
if submission == "new-submission":
context.submission = frappe._dict()
else:
context.submission = frappe.db.get_value(
"LMS Assignment Submission",
submission,
["name", "assignment_attachment", "comments", "status"],
as_dict=True,
)
if not context.assignment or not context.submission:
raise frappe.PermissionError(_("Invalid Submission URL"))

View File

@@ -1,80 +1,99 @@
{% extends "templates/base.html" %} {% extends "lms/templates/lms_base.html" %}
{% block title %} {% block title %}
{{ _("Assignments") }} {{ assignment.title if assignment.name else _("Assignment Details") }}
{% endblock %} {% endblock %}
{% block page_content %}
{% block content %}
<div class="common-page-style"> <div class="common-page-style">
<div class="container"> {{ Header() }}
<div class="common-card-style column-card medium"> <div class="container form-width">
<div class="course-home-headings"> {{ _("Assignment Grading") }} </div>
{{ AssignmentForm(assignment) }} {{ AssignmentForm(assignment) }}
</div> </div>
</div> </div>
</div>
{% endblock %} {% 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) %} {% macro AssignmentForm(assignment) %}
{% set course_title = frappe.db.get_value("LMS Course", assignment.course, "title") %} <article>
{% set lesson_title = frappe.db.get_value("Course Lesson", assignment.lesson, "title") %} <div class="field-parent">
<div class="field-group">
<form class="register-form"> <div class="field-label reqd"> {{ _("Title") }} </div>
<div class="field-description">
<div class="form-padding"> {{ _("Give the assignment a title.") }}
<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 />
</div> </div>
<input type="text" id="title" class="field-input" {% if assignment.name %} value="{{ assignment.title }}" data-name="{{ assignment.name }}" {% endif %}>
</div> </div>
<div class="form-row"> <div class="field-group">
<div class="col-md-6"> <div class="field-label reqd"> {{ "Type" }} </div>
<div class="form-group"> <div class="field-description">
<label> {{ _("Lesson") }} </label> {{ _("Select the format in which students will have to submit the assignment.") }}
<input type="text" class="form-control" value="{{ lesson_title }}" disabled readonly />
</div> </div>
<div class="field-input flex align-center">
<div class="form-group"> <select class="form-control" id="type">
<label> {{ _("Result") }} </label> {% set types = ["Document", "PDF", "URL", "Image"] %}
<div class="control-input flex align-center form-control"> {% for type in types %}
<select class="input-with-feedback form-control pl-0" id="result" <option value="{{ type }}" {% if assignment.type == type %} selected {% endif %}>
{% if not is_moderator %} disabled {% endif %} data-type="{{ assignment.status }}"> {{ type }}
<option selected> {{ _("Not Graded") }} </option> </option>
<option value="Pass"> {{ _("Pass") }} </option> {% endfor %}
<option value="Fail"> {{ _("Fail") }} </option>
</select> </select>
<div class="select-icon"> <div class="select-icon">
<svg class="icon icon-sm"> <svg class="icon icon-sm" style="">
<use class="" href="#icon-select"></use> <use class="" href="#icon-select"></use>
</svg> </svg>
</div> </div>
</div> </div>
</div>
</div> </div>
<div class="form-group col-md-6"> <div class="field-group">
<label>{{ _("Comments") }}</label> <div class="field-label reqd">
<textarea class="form-control" id="comments" {% if not is_moderator %} disabled readonly {% endif %} {{ _("Question") }}
>{% if assignment.comments %}{{ assignment.comments }}{% endif %}</textarea> </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>
</div> </article>
<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>
{% endmacro %} {% endmacro %}
{%- block script %}
{{ super() }}
{{ include_script('controls.bundle.js') }}
{% endblock %}

View File

@@ -1,46 +1,38 @@
frappe.ready(() => { frappe.ready(() => {
this.result; if ($("#question").length) {
let self = this; make_editor();
}
set_result(); $(".btn-save-assignment").click((e) => {
$("#save-assignment").click((e) => {
save_assignment(e); save_assignment(e);
}); });
$("#result").change((e) => {
$("#result option:selected").each(function () {
self.result = $(this).val();
});
});
}); });
const set_result = () => { const make_editor = () => {
let self = this; this.question = new frappe.ui.FieldGroup({
let result = $("#result").data("type"); fields: [
if (result) { {
$("#result option").each((i, elem) => { fieldname: "question",
if ($(elem).val() == result) { fieldtype: "Text Editor",
$(elem).attr("selected", true); default: $("#question-data").html(),
self.result = result; },
} ],
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) => { const save_assignment = (e) => {
e.preventDefault();
if (!["Pass", "Fail"].includes(this.result))
frappe.throw({
title: __("Not Graded"),
message: __("Please grade the assignment."),
});
frappe.call({ frappe.call({
method: "lms.lms.doctype.lms_assignment_submission.lms_assignment_submission.grade_assignment", method: "lms.lms.doctype.lms_assignment.lms_assignment.save_assignment",
args: { args: {
name: $(e.currentTarget).data("assignment"), assignment: $(e.currentTarget).data("assignment") || "",
result: this.result, title: $("#title").val(),
comments: $("#comments").val(), question: this.question.fields_dict["question"].value,
type: $("#type").val(),
}, },
callback: (data) => { callback: (data) => {
frappe.show_alert({ frappe.show_alert({
@@ -48,7 +40,7 @@ const save_assignment = (e) => {
indicator: "green", indicator: "green",
}); });
setTimeout(() => { setTimeout(() => {
window.history.go(-2); window.location.href = `/assignments/${data.message}`;
}, 2000); }, 2000);
}, },
}); });

View File

@@ -1,35 +1,23 @@
import frappe import frappe
from lms.lms.utils import has_course_moderator_role
from frappe import _ from frappe import _
from lms.lms.utils import can_create_courses
def get_context(context): def get_context(context):
context.no_cache = 1 context.no_cache = 1
assignment = frappe.form_dict["assignment"]
context.assignment = frappe.db.get_value( if not can_create_courses():
"LMS Assignment Submission", message = "You do not have permission to access this page."
assignment,
[
"assignment",
"comments",
"status",
"name",
"member",
"member_name",
"course",
"lesson",
],
as_dict=True,
)
context.is_moderator = has_course_moderator_role()
if (
not has_course_moderator_role()
and not frappe.session.user == context.assignment.member
):
message = "You don't have the permissions to access this page."
if frappe.session.user == "Guest": if frappe.session.user == "Guest":
message = "Please login to access this page." message = "Please login to access this page."
raise frappe.PermissionError(_(message)) 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
)

View 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 %}

View 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"],
)

View File

@@ -6,7 +6,7 @@ frappe.ready(() => {
} }
setup_editor(); setup_editor();
fetch_quiz_list(); //fetch_quiz_list();
$("#save-lesson").click((e) => { $("#save-lesson").click((e) => {
save_lesson(e); save_lesson(e);

View File

@@ -17,7 +17,6 @@ def get_context(context):
quizname = frappe.form_dict["quizname"] quizname = frappe.form_dict["quizname"]
if quizname == "new-quiz": if quizname == "new-quiz":
context.quiz = frappe._dict() context.quiz = frappe._dict()
context.quiz.edit_mode = 1
else: else:
fields_arr = ["name", "question", "type"] fields_arr = ["name", "question", "type"]
for num in range(1, 5): for num in range(1, 5):

View File

@@ -88,9 +88,21 @@
</li> </li>
{% if class_students | length and (is_moderator or is_student) %} {% if class_students | length and (is_moderator or is_student) %}
<li class="nav-item">
<a class="nav-link" data-toggle="tab" href="#assessments">
{{ _("Assessments") }}
<span class="course-list-count">
{{ assessments | length }}
</span>
</a>
</li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" data-toggle="tab" href="#live-class"> <a class="nav-link" data-toggle="tab" href="#live-class">
{{ _("Live Class") }} {{ _("Live Class") }}
<span class="course-list-count">
{{ live_classes | length }}
</span>
</a> </a>
</li> </li>
{% endif %} {% endif %}
@@ -109,6 +121,10 @@
</div> </div>
{% if class_students | length and (is_moderator or is_student) %} {% if class_students | length and (is_moderator or is_student) %}
<div class="tab-pane" id="assessments" role="tabpanel" aria-labelledby="assessments">
{{ AssessmentsSection(class_info) }}
</div>
<div class="tab-pane" id="live-class" role="tabpanel" aria-labelledby="live-class"> <div class="tab-pane" id="live-class" role="tabpanel" aria-labelledby="live-class">
{{ LiveClassSection(class_info, live_classes) }} {{ LiveClassSection(class_info, live_classes) }}
</div> </div>
@@ -159,7 +175,6 @@
</article> </article>
{% endmacro %} {% endmacro %}
@@ -182,8 +197,7 @@
<div> <div>
{% for student in class_students %} {% for student in class_students %}
{% set last_active = frappe.db.get_value("User", student.student, "last_active") %} {% 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 {% set allow_progress = is_moderator or student.student == frappe.session.user %}
is_student) %}
<div class="list-row level"> <div class="list-row level">
<div> <div>
@@ -209,6 +223,115 @@
{% endmacro %} {% 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">
<p class="alert alert-info">
{{ _("Please 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>
<form class="profile-column-grid" id="assessment-form">
{% if all_assignments | length %}
<div>
<div class="field-label mb-2">
{{ _("Assignments") }}
</div>
{% 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 class="field-label">
{{ _("Create New") }}
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary btn-sm mr-2 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-50">
{{ _("Title") }}
</div>
<div class="">
{{ _("Type") }}
</div>
</div>
{% for assessment in assessments %}
<div class="list-row level level-left">
<div class="w-50">
<a class="clickable" href="{{ assessment.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) %} {% macro LiveClassSection(class_info, live_classes) %}
<article> <article>
<header class="edit-header"> <header class="edit-header">
@@ -230,7 +353,6 @@
{% macro CreateLiveClass(class_info) %} {% macro CreateLiveClass(class_info) %}
{% if is_moderator %} {% if is_moderator %}
<div class="modal fade live-class-modal" id="live-class-modal" tabindex="-1" role="dialog"> <div class="modal fade live-class-modal" id="live-class-modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">

View File

@@ -1,4 +1,6 @@
frappe.ready(() => { frappe.ready(() => {
let self = this;
$(".btn-add-student").click((e) => { $(".btn-add-student").click((e) => {
show_student_modal(e); show_student_modal(e);
}); });
@@ -27,6 +29,19 @@ frappe.ready(() => {
$(".btn-remove-course").click((e) => { $(".btn-remove-course").click((e) => {
remove_course(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) => { const submit_student = (e) => {
@@ -453,3 +468,15 @@ const show_student_modal = () => {
$(".modal-body input").focus(); $(".modal-body input").focus();
}, 1000); }, 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"),
},
});
};

View File

@@ -2,6 +2,7 @@ import frappe
from lms.lms.utils import has_course_moderator_role from lms.lms.utils import has_course_moderator_role
from frappe import _ from frappe import _
from frappe.utils import getdate from frappe.utils import getdate
from lms.www.utils import get_assessments
def get_context(context): def get_context(context):
@@ -9,6 +10,7 @@ def get_context(context):
class_name = frappe.form_dict["classname"] class_name = frappe.form_dict["classname"]
session_user = [] session_user = []
remaining_students = [] remaining_students = []
context.is_moderator = has_course_moderator_role()
context.class_info = frappe.db.get_value( context.class_info = frappe.db.get_value(
"LMS Class", "LMS Class",
@@ -56,8 +58,6 @@ def get_context(context):
else: else:
context.class_students = class_students context.class_students = class_students
context.is_moderator = has_course_moderator_role()
students = [student.student for student in class_students] students = [student.student for student in class_students]
context.is_student = frappe.session.user in students context.is_student = frappe.session.user in students
@@ -67,3 +67,33 @@ def get_context(context):
["title", "description", "time", "date", "start_url", "join_url", "owner"], ["title", "description", "time", "date", "start_url", "join_url", "owner"],
order_by="date", order_by="date",
) )
context.assessments = get_assessments(context.is_moderator, 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,
}
)
context.all_assignments = all_assignments
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,
}
)
context.all_quizzes = all_quizzes

View File

@@ -6,80 +6,61 @@
{% block page_content %} {% block page_content %}
<div class="common-page-style"> <div class="common-page-style">
<div class="container"> {{ Header() }}
{{ BreadCrumb(class_info, student) }} <div class="container form-width">
<div class="common-card-style flex-column p-5"> {{ Progress() }}
<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>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% macro Header() %}
{% macro BreadCrumb(class_info, student) %} <header class="sticky mb-5">
<div class="breadcrumb"> <div class="container form-width">
<a class="dark-links" href="/classes">{{ _("All Classes") }}</a> <div class="edit-header">
<img class="ml-1 mr-1" src="/assets/lms/icons/chevron-right.svg"> <div>
<a class="dark-links" href="/classes/{{ class_info.name }}">{{ class_info.name }}</a> <div class="page-title">
<img class="ml-1 mr-1" src="/assets/lms/icons/chevron-right.svg"> {{ _("{0}'s Progress").format(student.full_name) }}
<span class="breadcrumb-destination">{{ student.full_name }}</span>
</div> </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>
<div class="align-self-center">
<a class="btn btn-secondary btn-sm btn-evaluate" href=/evaluation/new?member={{student.name}}&date={{frappe.utils.getdate()}}&class={{class_info.name}}">
{{ _("Save") }}
</a>
</div>
</div>
</div>
</header>
{% endmacro %} {% endmacro %}
{% macro Progress(class_info, student) %} {% macro Progress(class_info, student) %}
<div> <div>
{% for course in class_courses %} {% for assessment in assessments %}
{% set progress = course.membership.progress %} <div>
<div class="medium"> <div>
<div class="progress-course-header"> {{ assessment.title }}
<div class="section-heading"> {{ course.title }} </div>
<div class="ml-3"> {{ frappe.utils.cint(course.membership.progress) }}% </div>
</div> </div>
{{ assessment.submission}}
{% if course.quizzes | length or course.assignments | length or course.evaluations | length %} {% if assessment.submission %}
<div class="{% if not loop.last %} my-5 {% else %} mt-5 {% endif %}"> <div>
<table class="table"> {{ assessment.submission.status }}
<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> </div>
<a class="btn btn-secondary btn-sm" href="{{ assessment.url }}">
{{ _("Grade") }}
</a>
{% endif %} {% endif %}
</div> </div>
{% endfor %} {% endfor %}

View File

@@ -1,13 +1,14 @@
import frappe import frappe
from lms.lms.utils import has_course_moderator_role from lms.lms.utils import has_course_moderator_role
from frappe import _ from frappe import _
from lms.www.utils import get_assessments
def get_context(context): def get_context(context):
context.no_cache = 1 context.no_cache = 1
student = frappe.form_dict["username"] student = frappe.form_dict["username"]
classname = frappe.form_dict["classname"] class_name = frappe.form_dict["classname"]
context.is_moderator = has_course_moderator_role() context.is_moderator = has_course_moderator_role()
context.student = frappe.db.get_value( context.student = frappe.db.get_value(
@@ -17,32 +18,9 @@ def get_context(context):
as_dict=True, as_dict=True,
) )
context.class_info = frappe.db.get_value( 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( context.assessments = get_assessments(
"Class Course", {"parent": classname}, ["course", "title"] context.is_moderator, class_name, context.student.name
) )
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

View File

@@ -57,3 +57,50 @@ def get_current_lesson_details(lesson_number, context, is_edit=False):
lesson_info = details_list[0] lesson_info = details_list[0]
lesson_info.body = lesson_info.body.replace('"', "'") lesson_info.body = lesson_info.body.replace('"', "'")
return lesson_info return lesson_info
def get_assessments(is_moderator, class_name, member):
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"
)
if is_moderator:
assessment.url = f"/assignments/{assessment.assessment_name}"
else:
existing_submission = frappe.db.exists(
{
"doctype": "LMS Assignment Submission",
"member": member,
"assignment": assessment.assessment_name,
}
)
print(existing_submission)
if existing_submission:
assessment.submission = frappe.db.get_value(
"LMS Assignment Submission",
existing_submission,
["name", "grade", "comments"],
as_dict=True,
)
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