Merge pull request #554 from pateljannat/class-ui
fix: class improvements
This commit is contained in:
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
node-version: '18'
|
||||
check-latest: true
|
||||
- name: setup cache for bench
|
||||
uses: actions/cache@v2
|
||||
|
||||
2
.github/workflows/ui-tests.yml
vendored
2
.github/workflows/ui-tests.yml
vendored
@@ -50,7 +50,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
check-latest: true
|
||||
|
||||
- name: Add to Hosts
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-05-29 14:50:55.259990",
|
||||
"modified": "2023-06-26 18:09:29.809564",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Assignment",
|
||||
@@ -64,9 +64,23 @@
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Moderator",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"show_title_field_in_link": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
"states": [],
|
||||
"title_field": "title"
|
||||
}
|
||||
@@ -50,7 +50,8 @@
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Description"
|
||||
"label": "Description",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_6",
|
||||
@@ -137,7 +138,7 @@
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-06-15 12:30:26.929156",
|
||||
"modified": "2023-06-22 15:57:25.190084",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Class",
|
||||
|
||||
@@ -2,19 +2,21 @@
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe import _
|
||||
from frappe.utils import cint, format_date, format_datetime
|
||||
import requests
|
||||
import base64
|
||||
import json
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cint, format_date, format_datetime
|
||||
|
||||
|
||||
class LMSClass(Document):
|
||||
def validate(self):
|
||||
if self.seat_count:
|
||||
self.validate_seats_left()
|
||||
self.validate_duplicate_courses()
|
||||
self.validate_duplicate_students()
|
||||
self.validate_duplicate_assessments()
|
||||
self.validate_membership()
|
||||
|
||||
def validate_duplicate_students(self):
|
||||
@@ -27,6 +29,28 @@ class LMSClass(Document):
|
||||
)
|
||||
)
|
||||
|
||||
def validate_duplicate_courses(self):
|
||||
courses = [row.course for row in self.courses]
|
||||
duplicates = {course for course in courses if courses.count(course) > 1}
|
||||
if len(duplicates):
|
||||
title = frappe.db.get_value("LMS Course", next(iter(duplicates)), "title")
|
||||
frappe.throw(
|
||||
_("Course {0} has already been added to this class.").format(frappe.bold(title))
|
||||
)
|
||||
|
||||
def validate_duplicate_assessments(self):
|
||||
assessments = [row.assessment_name for row in self.assessment]
|
||||
for assessment in self.assessment:
|
||||
if assessments.count(assessment.assessment_name) > 1:
|
||||
title = frappe.db.get_value(
|
||||
assessment.assessment_type, assessment.assessment_name, "title"
|
||||
)
|
||||
frappe.throw(
|
||||
_("Assessment {0} has already been added to this class.").format(
|
||||
frappe.bold(title)
|
||||
)
|
||||
)
|
||||
|
||||
def validate_membership(self):
|
||||
for course in self.courses:
|
||||
for student in self.students:
|
||||
@@ -43,51 +67,30 @@ class LMSClass(Document):
|
||||
frappe.throw(_("There are no seats available in this class."))
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_student(email, class_name):
|
||||
if not frappe.db.exists("User", email):
|
||||
frappe.throw(_("There is no such user. Please create a user with this Email ID."))
|
||||
|
||||
filters = {
|
||||
"student": email,
|
||||
"parent": class_name,
|
||||
"parenttype": "LMS Class",
|
||||
"parentfield": "students",
|
||||
}
|
||||
if frappe.db.exists("Class Student", filters):
|
||||
frappe.throw(
|
||||
_("Student {0} has already been added to this class.").format(frappe.bold(email))
|
||||
)
|
||||
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Class Student",
|
||||
"student": email,
|
||||
"student_name": frappe.db.get_value("User", email, "full_name"),
|
||||
"parent": class_name,
|
||||
"parenttype": "LMS Class",
|
||||
"parentfield": "students",
|
||||
}
|
||||
).save()
|
||||
return True
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def remove_student(student, class_name):
|
||||
frappe.only_for("Moderator")
|
||||
frappe.db.delete("Class Student", {"student": student, "parent": class_name})
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def remove_course(course, parent):
|
||||
frappe.only_for("Moderator")
|
||||
frappe.db.delete("Class Course", {"course": course, "parent": parent})
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def remove_assessment(assessment, parent):
|
||||
frappe.only_for("Moderator")
|
||||
frappe.db.delete("LMS Assessment", {"assessment_name": assessment, "parent": parent})
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_live_class(
|
||||
class_name, title, duration, date, time, timezone, auto_recording, description=None
|
||||
):
|
||||
date = format_date(date, "yyyy-mm-dd", True)
|
||||
|
||||
frappe.only_for("Moderator")
|
||||
payload = {
|
||||
"topic": title,
|
||||
"start_time": format_datetime(f"{date} {time}", "yyyy-MM-ddTHH:mm:ssZ"),
|
||||
@@ -164,6 +167,7 @@ def create_class(
|
||||
category=None,
|
||||
name=None,
|
||||
):
|
||||
frappe.only_for("Moderator")
|
||||
if name:
|
||||
class_details = frappe.get_doc("LMS Class", name)
|
||||
else:
|
||||
@@ -184,23 +188,3 @@ 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()
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-06-21 09:13:01.322701",
|
||||
"modified": "2023-06-23 12:35:25.204131",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Quiz",
|
||||
@@ -87,6 +87,18 @@
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Moderator",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"show_title_field_in_link": 1,
|
||||
|
||||
@@ -733,3 +733,23 @@ def is_onboarding_complete():
|
||||
if course_created
|
||||
else None,
|
||||
}
|
||||
|
||||
|
||||
def has_submitted_assessment(assessment, type, member=None):
|
||||
if not member:
|
||||
member = frappe.session.user
|
||||
|
||||
doctype = (
|
||||
"LMS Assignment Submission" if type == "LMS Assignment" else "LMS Quiz Submission"
|
||||
)
|
||||
docfield = "assignment" if type == "LMS Assignment" else "quiz"
|
||||
|
||||
filters = {}
|
||||
filters[docfield] = assessment
|
||||
filters["member"] = member
|
||||
return frappe.db.exists(doctype, filters)
|
||||
|
||||
|
||||
def has_graded_assessment(submission):
|
||||
status = frappe.db.get_value("LMS Assignment Submission", submission, "status")
|
||||
return False if status == "Not Graded" else True
|
||||
|
||||
@@ -72,6 +72,9 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="course-card-title">{{ course.title }}</div>
|
||||
<div class="short-introduction">
|
||||
{{ course.short_introduction }}
|
||||
</div>
|
||||
|
||||
{% if membership and not is_instructor(course.name) and not read_only %}
|
||||
<div class="progress">
|
||||
|
||||
@@ -53,4 +53,6 @@ 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
|
||||
lms.patches.v1_0.rename_assignment_doctype
|
||||
execute:frappe.permissions.reset_perms("LMS Assignment")
|
||||
execute:frappe.permissions.reset_perms("LMS Quiz")
|
||||
|
||||
@@ -374,10 +374,20 @@ input[type=checkbox] {
|
||||
.course-card-title {
|
||||
font-weight: 600;
|
||||
color: var(--gray-900);
|
||||
margin-bottom: 1.25rem;
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
.short-introduction {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.card-divider {
|
||||
border-top: 1px solid var(--gray-300);
|
||||
margin-bottom: 1rem;
|
||||
@@ -1385,6 +1395,10 @@ pre {
|
||||
margin: 0 1rem;
|
||||
}
|
||||
|
||||
.seperator::after {
|
||||
content: "\00B7";
|
||||
}
|
||||
|
||||
.course-overlay-card {
|
||||
background-color: white;
|
||||
border-radius: var(--border-radius-lg);
|
||||
@@ -1890,6 +1904,14 @@ li {
|
||||
padding: 0 1rem !important;
|
||||
}
|
||||
|
||||
.modal-dialog-scrollable .modal-body {
|
||||
overflow-y: unset;
|
||||
}
|
||||
|
||||
.modal-dialog-scrollable .modal-content {
|
||||
overflow: unset;
|
||||
}
|
||||
|
||||
.modal-header, .modal-body {
|
||||
margin-bottom: 0.5rem !important;
|
||||
}
|
||||
@@ -2167,4 +2189,25 @@ select {
|
||||
|
||||
.awesomplete ul li:last-child {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.students-parent {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, 150px);
|
||||
grid-gap: 1rem;
|
||||
}
|
||||
|
||||
.btn-remove-course {
|
||||
opacity: 0;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.btn-remove-course:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.rows .grid-row .data-row,
|
||||
.rows .grid-row .grid-footer-toolbar,
|
||||
.grid-form-heading {
|
||||
cursor: none;
|
||||
}
|
||||
@@ -326,6 +326,7 @@ const open_class_dialog = () => {
|
||||
label: __("Description"),
|
||||
fieldname: "description",
|
||||
default: class_info && class_info.description,
|
||||
reqd: 1,
|
||||
},
|
||||
],
|
||||
primary_action_label: __("Save"),
|
||||
|
||||
@@ -34,23 +34,46 @@
|
||||
<div class="page-title">
|
||||
{{ class_info.title }}
|
||||
</div>
|
||||
<div class="mt-1">
|
||||
<svg class="icon icon-sm">
|
||||
<use href="#icon-calendar"></use>
|
||||
</svg>
|
||||
<span>
|
||||
{{ frappe.utils.format_date(class_info.start_date, "long") }} -
|
||||
</span>
|
||||
<span>
|
||||
{{ frappe.utils.format_date(class_info.end_date, "long") }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{% if class_info.description %}
|
||||
<div class="mt-1">
|
||||
<div class="mb-4">
|
||||
{{ class_info.description }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="vertically-center">
|
||||
<div class="">
|
||||
<svg class="icon icon-sm">
|
||||
<use href="#icon-calendar"></use>
|
||||
</svg>
|
||||
<span>
|
||||
{{ frappe.utils.format_date(class_info.start_date, "long") }} -
|
||||
</span>
|
||||
<span>
|
||||
{{ frappe.utils.format_date(class_info.end_date, "long") }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span class="seperator"></span>
|
||||
|
||||
<div class="">
|
||||
<svg class="icon icon-md">
|
||||
<use href="#icon-education"></use>
|
||||
</svg>
|
||||
{{ class_courses | length }} {{ _("Courses") }}
|
||||
</div>
|
||||
|
||||
<span class="seperator"></span>
|
||||
|
||||
<div class="">
|
||||
<svg class="icon icon-md">
|
||||
<use href="#icon-users"></use>
|
||||
</svg>
|
||||
{{ class_students | length }} {{ _("Students") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% if class_info.custom_component %}
|
||||
{{ class_info.custom_component }}
|
||||
{% endif %}
|
||||
@@ -155,20 +178,17 @@
|
||||
</header>
|
||||
|
||||
{% if class_courses | length %}
|
||||
<div>
|
||||
<div class="cards-parent">
|
||||
{% for course in class_courses %}
|
||||
<div class="list-row level">
|
||||
<a class="clickable" href="/courses/{{ course.course }}">
|
||||
{{ course.title }}
|
||||
</a>
|
||||
{% if is_moderator %}
|
||||
<div type="button" class="btn-remove-course" data-course="{{ course.course }}">
|
||||
<div>
|
||||
{{ widgets.CourseCard(course=course, read_only=False) }}
|
||||
<button class="btn icon-btn btn-default btn-block btn-remove-course" data-course="{{ course.name }}">
|
||||
<svg class="icon icon-sm">
|
||||
<use href="#icon-delete"></use>
|
||||
</svg>
|
||||
</div>
|
||||
{% endif %}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
@@ -177,7 +197,6 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
</article>
|
||||
{% endmacro %}
|
||||
|
||||
@@ -198,26 +217,63 @@
|
||||
</header>
|
||||
|
||||
{% if class_students | length %}
|
||||
<div>
|
||||
<div class="form-grid">
|
||||
<div class="grid-heading-row">
|
||||
<div class="grid-row">
|
||||
<div class="data-row row">
|
||||
<div class="col grid-static-col">
|
||||
{{ _("Full Name") }}
|
||||
</div>
|
||||
<div class="col grid-static-col col-xs-2 text-right">
|
||||
{{ _("Courses Completed") }}
|
||||
</div>
|
||||
<div class="col grid-static-col col-xs-2 text-right">
|
||||
{{ _("Assessments Completed") }}
|
||||
</div>
|
||||
<div class="col grid-static-col col-xs-2 text-right">
|
||||
{{ _("Assessments Graded") }}
|
||||
</div>
|
||||
<div class="col grid-static-col">
|
||||
{{ _("Last Active") }}
|
||||
</div>
|
||||
{% if is_moderator %}
|
||||
<div class="col grid-static-col col-xs-1">
|
||||
<svg class="icon icon-sm" style="filter: opacity(0.5)">
|
||||
<use class="" href="#icon-setting-gear"></use>
|
||||
</svg>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</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 %}
|
||||
|
||||
<div class="list-row level">
|
||||
<div>
|
||||
<a {% if allow_progress %} class="clickable" href="/classes/{{ class_info.name }}/students/{{ student.username }}" {% endif %}>
|
||||
<div class="grid-row">
|
||||
<div class="data-row row">
|
||||
<a class="col grid-static-col {% if allow_progress %} clickable {% endif %}" {% if allow_progress %} href="/classes/{{ class_info.name }}/students/{{ student.username }}" {% endif %}>
|
||||
{{ student.student_name }}
|
||||
</a>
|
||||
<div class="col grid-static-col col-xs-2 text-right">
|
||||
{{ student.courses_completed }}
|
||||
</div>
|
||||
<div class="col grid-static-col col-xs-2 text-right">
|
||||
{{ student.assessments_completed }}
|
||||
</div>
|
||||
<div class="col grid-static-col col-xs-2 text-right">
|
||||
{{ student.assessments_graded }}
|
||||
</div>
|
||||
<div class="col grid-static-col">
|
||||
{{ frappe.utils.pretty_date(student.last_active) }}
|
||||
</div>
|
||||
{% if is_moderator %}
|
||||
<div type="button" class="col grid-static-col col-xs-1 btn-remove-student" data-student="{{ student.student }}">
|
||||
<svg class="icon icon-sm">
|
||||
<use href="#icon-delete"></use>
|
||||
</svg>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if is_moderator %}
|
||||
<div type="button" class="btn-remove-student" data-student="{{ student.student }}">
|
||||
<svg class="icon icon-sm">
|
||||
<use href="#icon-delete"></use>
|
||||
</svg>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
@@ -239,117 +295,52 @@
|
||||
</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 or quiz for this class, click on the buttons below. Once you have created the new assignment or quiz you can come back and add it from here.") }}
|
||||
</p>
|
||||
<div class="flex">
|
||||
<a class="btn btn-default btn-sm" href="/assignments/new-assignment">
|
||||
{{ _("Create Assignment") }}
|
||||
</a>
|
||||
<a class="btn btn-default btn-sm ml-2" href="/quizzes/new-quiz">
|
||||
{{ _("Create Quiz") }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form class="" id="assessment-form">
|
||||
<div class="field-label mb-2">
|
||||
{{ _("Select Assessments") }}
|
||||
</div>
|
||||
<p class="field-description">
|
||||
{{ _("Select the assessments you wish to include for this class. Your selections will be automatically saved upon clicking. If you decide to remove an item from the list, simply uncheck it.") }}
|
||||
</p>
|
||||
<div class="flex justify-content-between">
|
||||
{% 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">
|
||||
{{ _("Quizzes") }}
|
||||
</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 %}
|
||||
</div>
|
||||
|
||||
</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 class="form-grid">
|
||||
<div class="grid-heading-row">
|
||||
<div class="grid-row">
|
||||
<div class="row data-row">
|
||||
<div class="col grid-static-col">
|
||||
{{ _("Title") }}
|
||||
</div>
|
||||
<div class="col grid-static-col">
|
||||
{{ _("Type") }}
|
||||
</div>
|
||||
<div class="col grid-static-col col-xs-1">
|
||||
<svg class="icon icon-sm" style="filter: opacity(0.5)">
|
||||
<use href="#icon-setting-gear"></use>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</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 class="grid-body">
|
||||
<div class="rows">
|
||||
{% for assessment in assessments %}
|
||||
<div class="grid-row">
|
||||
<div class="row data-row">
|
||||
<a class="col grid-static-col clickable" href="{{ assessment.edit_url }}">
|
||||
{{ assessment.title }}
|
||||
</a>
|
||||
<div class="col grid-static-col">
|
||||
{{ assessment.assessment_type.split("LMS ")[1] }}
|
||||
</div>
|
||||
<div type="button" class="col grid-static-col col-xs-1 btn-remove-assessment" data-assessment="{{ assessment.assessment_name }}">
|
||||
<svg class="icon icon-sm">
|
||||
<use href="#icon-delete"></use>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-muted mt-3"> {{ _("No Assessments") }} </p>
|
||||
@@ -376,7 +367,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">
|
||||
@@ -404,7 +394,6 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
@@ -412,52 +401,51 @@
|
||||
<div class="lms-card-parent mt-5">
|
||||
{% if live_classes | length %}
|
||||
{% for class in live_classes %}
|
||||
<div class="lms-card">
|
||||
<div class="common-card-style column-card">
|
||||
|
||||
<div class="mb-0">
|
||||
<div class="dropdown pull-right">
|
||||
<svg class="icon icon-sm dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<use href="#icon-dot-horizontal"></use>
|
||||
<div class="mb-0">
|
||||
<div class="dropdown pull-right">
|
||||
<svg class="icon icon-sm dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<use href="#icon-dot-horizontal"></use>
|
||||
</svg>
|
||||
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton">
|
||||
{% if class.owner == frappe.session.user %}
|
||||
<li>
|
||||
<a class="dropdown-item small" href="{{ class.start_url }}"> {{ _("Start") }} </a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if is_student %}
|
||||
<li>
|
||||
<a class="dropdown-item small" href="{{ class.join_url }}"> {{ _("Join") }} </a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="bold-heading mb-4">
|
||||
{{ class.title }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<svg class="icon icon-sm">
|
||||
<use href="#icon-calendar"></use>
|
||||
</svg>
|
||||
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton">
|
||||
{% if class.owner == frappe.session.user %}
|
||||
<li>
|
||||
<a class="dropdown-item small" href="{{ class.start_url }}"> {{ _("Start") }} </a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if is_student %}
|
||||
<li>
|
||||
<a class="dropdown-item small" href="{{ class.join_url }}"> {{ _("Join") }} </a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
</ul>
|
||||
{{ frappe.utils.format_date(class.date, "full") }}
|
||||
</div>
|
||||
<div class="lms-card-title mb-4">
|
||||
{{ class.title }}
|
||||
<div>
|
||||
<svg class="icon icon-sm">
|
||||
<use href="#icon-clock"></use>
|
||||
</svg>
|
||||
{{ frappe.utils.format_time(class.time, "hh:mm a") }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<svg class="icon icon-sm">
|
||||
<use href="#icon-calendar"></use>
|
||||
</svg>
|
||||
{{ frappe.utils.format_date(class.date, "full") }}
|
||||
</div>
|
||||
<div>
|
||||
<svg class="icon icon-sm">
|
||||
<use href="#icon-clock"></use>
|
||||
</svg>
|
||||
{{ frappe.utils.format_time(class.time, "hh:mm a") }}
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
{{ class.description }}
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
{{ class.description }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p class=""> {{ _("No Live Classes") }} </p>
|
||||
<p class=""> {{ _("No Live Classes") }} </p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -468,10 +456,12 @@
|
||||
<script>
|
||||
frappe.boot.user = {
|
||||
"can_create": [],
|
||||
"can_select": ["User", "LMS Category"],
|
||||
"can_read": ["User", "LMS Category"]
|
||||
"can_select": ["User", "LMS Category", "LMS Assignment", "LMS Quiz"],
|
||||
"can_read": ["User", "LMS Category", "LMS Assignment", "LMS Quiz"]
|
||||
};
|
||||
|
||||
frappe.boot.single_types = []
|
||||
|
||||
frappe.router = {
|
||||
slug (name) {
|
||||
return name.toLowerCase().replace(/ /g, "-");
|
||||
|
||||
@@ -30,68 +30,20 @@ frappe.ready(() => {
|
||||
remove_course(e);
|
||||
});
|
||||
|
||||
$(".btn-remove-assessment").click((e) => {
|
||||
remove_assessment(e);
|
||||
});
|
||||
|
||||
$("#open-assessment-modal").click((e) => {
|
||||
e.preventDefault();
|
||||
$("#assessment-modal").modal("show");
|
||||
show_assessment_modal();
|
||||
});
|
||||
|
||||
$(".assessment-item").click((e) => {
|
||||
update_assessment(e);
|
||||
});
|
||||
|
||||
$(".btn-assessment-close").click((e) => {
|
||||
$(".btn-close").click((e) => {
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
||||
|
||||
const submit_student = (e) => {
|
||||
e.preventDefault();
|
||||
if ($('input[data-fieldname="student_input"]').val()) {
|
||||
frappe.call({
|
||||
method: "lms.lms.doctype.lms_class.lms_class.add_student",
|
||||
args: {
|
||||
email: $('input[data-fieldname="student_input"]').val(),
|
||||
class_name: $(".class-details").data("class"),
|
||||
},
|
||||
callback: (data) => {
|
||||
frappe.show_alert(
|
||||
{
|
||||
message: __("Student added successfully"),
|
||||
indicator: "green",
|
||||
},
|
||||
3
|
||||
);
|
||||
window.location.reload();
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const remove_student = (e) => {
|
||||
frappe.confirm(
|
||||
"Are you sure you want to remove this student from the class?",
|
||||
() => {
|
||||
frappe.call({
|
||||
method: "lms.lms.doctype.lms_class.lms_class.remove_student",
|
||||
args: {
|
||||
student: $(e.currentTarget).data("student"),
|
||||
class_name: $(".class-details").data("class"),
|
||||
},
|
||||
callback: (data) => {
|
||||
frappe.show_alert(
|
||||
{
|
||||
message: __("Student removed successfully"),
|
||||
indicator: "green",
|
||||
},
|
||||
3
|
||||
);
|
||||
window.location.reload();
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const create_live_class = (e) => {
|
||||
let class_name = $(".class-details").data("class");
|
||||
frappe.call({
|
||||
@@ -349,45 +301,39 @@ const show_course_modal = () => {
|
||||
fieldname: "course",
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
fieldtype: "HTML",
|
||||
fieldname: "instructions",
|
||||
label: __("Instructions"),
|
||||
options: __("Select a course to add to this class."),
|
||||
},
|
||||
],
|
||||
primary_action_label: __("Add"),
|
||||
primary_action(values) {
|
||||
frappe.call({
|
||||
method: "frappe.client.insert",
|
||||
args: {
|
||||
doc: {
|
||||
doctype: "Class Course",
|
||||
course: values.course,
|
||||
parenttype: "LMS Class",
|
||||
parentfield: "courses",
|
||||
parent: $(".class-details").data("class"),
|
||||
},
|
||||
},
|
||||
callback(r) {
|
||||
frappe.show_alert(
|
||||
{
|
||||
message: __("Course Added"),
|
||||
indicator: "green",
|
||||
},
|
||||
3
|
||||
);
|
||||
window.location.reload();
|
||||
},
|
||||
});
|
||||
add_course(values);
|
||||
course_modal.hide();
|
||||
},
|
||||
});
|
||||
course_modal.show();
|
||||
setTimeout(() => {
|
||||
$(".modal-body").css("min-height", "200px");
|
||||
$(".modal-body input").focus();
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const add_course = (values) => {
|
||||
frappe.call({
|
||||
method: "frappe.client.insert",
|
||||
args: {
|
||||
doc: {
|
||||
doctype: "Class Course",
|
||||
course: values.course,
|
||||
parenttype: "LMS Class",
|
||||
parentfield: "courses",
|
||||
parent: $(".class-details").data("class"),
|
||||
},
|
||||
},
|
||||
callback(r) {
|
||||
frappe.show_alert(
|
||||
{
|
||||
message: __("Course Added"),
|
||||
indicator: "green",
|
||||
},
|
||||
2000
|
||||
);
|
||||
window.location.reload();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const remove_course = (e) => {
|
||||
@@ -404,7 +350,7 @@ const remove_course = (e) => {
|
||||
message: __("Course Removed"),
|
||||
indicator: "green",
|
||||
},
|
||||
3
|
||||
2000
|
||||
);
|
||||
window.location.reload();
|
||||
},
|
||||
@@ -426,57 +372,145 @@ const show_student_modal = () => {
|
||||
ignore_user_type: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldtype: "HTML",
|
||||
fieldname: "instructions",
|
||||
label: __("Instructions"),
|
||||
options: __(
|
||||
"Please ensure a user account exists for the student before adding them to the class. Only users can be enrolled as students."
|
||||
),
|
||||
},
|
||||
],
|
||||
primary_action_label: __("Add"),
|
||||
primary_action(values) {
|
||||
frappe.call({
|
||||
method: "frappe.client.insert",
|
||||
args: {
|
||||
doc: {
|
||||
doctype: "Class Student",
|
||||
student: values.student,
|
||||
parenttype: "LMS Class",
|
||||
parentfield: "students",
|
||||
parent: $(".class-details").data("class"),
|
||||
},
|
||||
},
|
||||
callback(r) {
|
||||
frappe.show_alert(
|
||||
{
|
||||
message: __("Student Added"),
|
||||
indicator: "green",
|
||||
},
|
||||
3
|
||||
);
|
||||
window.location.reload();
|
||||
},
|
||||
});
|
||||
add_student(values);
|
||||
student_modal.hide();
|
||||
},
|
||||
});
|
||||
student_modal.show();
|
||||
setTimeout(() => {
|
||||
$(".modal-body").css("min-height", "200px");
|
||||
$(".modal-body input").focus();
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const update_assessment = (e) => {
|
||||
const add_student = (values) => {
|
||||
frappe.call({
|
||||
method: "lms.lms.doctype.lms_class.lms_class.update_assessment",
|
||||
method: "frappe.client.insert",
|
||||
args: {
|
||||
type: $(e.currentTarget).data("type"),
|
||||
name: $(e.currentTarget).data("name"),
|
||||
value: $(e.currentTarget).prop("checked") ? 1 : 0,
|
||||
class_name: $(".class-details").data("class"),
|
||||
doc: {
|
||||
doctype: "Class Student",
|
||||
student: values.student,
|
||||
parenttype: "LMS Class",
|
||||
parentfield: "students",
|
||||
parent: $(".class-details").data("class"),
|
||||
},
|
||||
},
|
||||
callback(r) {
|
||||
frappe.show_alert(
|
||||
{
|
||||
message: __("Student Added"),
|
||||
indicator: "green",
|
||||
},
|
||||
2000
|
||||
);
|
||||
window.location.reload();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const remove_student = (e) => {
|
||||
frappe.confirm(
|
||||
"Are you sure you want to remove this student from the class?",
|
||||
() => {
|
||||
frappe.call({
|
||||
method: "lms.lms.doctype.lms_class.lms_class.remove_student",
|
||||
args: {
|
||||
student: $(e.currentTarget).data("student"),
|
||||
class_name: $(".class-details").data("class"),
|
||||
},
|
||||
callback: (data) => {
|
||||
frappe.show_alert(
|
||||
{
|
||||
message: __("Student removed successfully"),
|
||||
indicator: "green",
|
||||
},
|
||||
2000
|
||||
);
|
||||
window.location.reload();
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const show_assessment_modal = (e) => {
|
||||
let assessment_modal = new frappe.ui.Dialog({
|
||||
title: "Manage Assessments",
|
||||
fields: [
|
||||
{
|
||||
fieldtype: "Link",
|
||||
options: "DocType",
|
||||
label: __("Assessment Type"),
|
||||
fieldname: "assessment_type",
|
||||
reqd: 1,
|
||||
get_query: () => {
|
||||
return {
|
||||
filters: {
|
||||
name: ["in", ["LMS Assignment", "LMS Quiz"]],
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldtype: "Dynamic Link",
|
||||
options: "assessment_type",
|
||||
label: __("Assessment"),
|
||||
fieldname: "assessment_name",
|
||||
reqd: 1,
|
||||
},
|
||||
],
|
||||
primary_action_label: __("Add"),
|
||||
primary_action(values) {
|
||||
add_addessment(values);
|
||||
assessment_modal.hide();
|
||||
},
|
||||
});
|
||||
assessment_modal.show();
|
||||
};
|
||||
|
||||
const add_addessment = (values) => {
|
||||
frappe.call({
|
||||
method: "frappe.client.insert",
|
||||
args: {
|
||||
doc: {
|
||||
doctype: "LMS Assessment",
|
||||
assessment_type: values.assessment_type,
|
||||
assessment_name: values.assessment_name,
|
||||
parenttype: "LMS Class",
|
||||
parentfield: "assessment",
|
||||
parent: $(".class-details").data("class"),
|
||||
},
|
||||
},
|
||||
callback(r) {
|
||||
frappe.show_alert(
|
||||
{
|
||||
message: __("Assessment Added"),
|
||||
indicator: "green",
|
||||
},
|
||||
2000
|
||||
);
|
||||
window.location.reload();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const remove_assessment = (e) => {
|
||||
frappe.confirm("Are you sure you want to remove this assessment?", () => {
|
||||
frappe.call({
|
||||
method: "lms.lms.doctype.lms_class.lms_class.remove_assessment",
|
||||
args: {
|
||||
assessment: $(e.currentTarget).data("assessment"),
|
||||
parent: $(".class-details").data("class"),
|
||||
},
|
||||
callback(r) {
|
||||
frappe.show_alert(
|
||||
{
|
||||
message: __("Assessment Removed"),
|
||||
indicator: "green",
|
||||
},
|
||||
2000
|
||||
);
|
||||
window.location.reload();
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import frappe
|
||||
from lms.lms.utils import has_course_moderator_role
|
||||
from frappe import _
|
||||
import frappe
|
||||
from frappe.utils import getdate
|
||||
from lms.www.utils import get_assessments
|
||||
from lms.lms.utils import (
|
||||
has_course_moderator_role,
|
||||
get_course_progress,
|
||||
has_submitted_assessment,
|
||||
has_graded_assessment,
|
||||
)
|
||||
|
||||
|
||||
def get_context(context):
|
||||
context.no_cache = 1
|
||||
class_name = frappe.form_dict["classname"]
|
||||
session_user = []
|
||||
remaining_students = []
|
||||
context.is_moderator = has_course_moderator_role()
|
||||
|
||||
context.class_info = frappe.db.get_value(
|
||||
@@ -21,12 +24,12 @@ def get_context(context):
|
||||
"start_date",
|
||||
"end_date",
|
||||
"description",
|
||||
"medium",
|
||||
"custom_component",
|
||||
"seat_count",
|
||||
"start_time",
|
||||
"end_time",
|
||||
"category",
|
||||
"medium",
|
||||
],
|
||||
as_dict=True,
|
||||
)
|
||||
@@ -35,34 +38,20 @@ def get_context(context):
|
||||
"LMS Course", {"published": 1}, ["name", "title"]
|
||||
)
|
||||
|
||||
context.class_courses = frappe.get_all(
|
||||
class_courses = frappe.get_all(
|
||||
"Class Course",
|
||||
{"parent": class_name},
|
||||
["name", "course", "title"],
|
||||
["name", "course"],
|
||||
order_by="creation desc",
|
||||
)
|
||||
|
||||
class_students = frappe.get_all(
|
||||
"Class Student",
|
||||
{"parent": class_name},
|
||||
["student", "student_name", "username"],
|
||||
["name", "student", "student_name", "username"],
|
||||
order_by="creation desc",
|
||||
)
|
||||
|
||||
for student in class_students:
|
||||
if student.student == frappe.session.user:
|
||||
session_user.append(student)
|
||||
else:
|
||||
remaining_students.append(student)
|
||||
|
||||
if len(session_user):
|
||||
context.class_students = session_user + remaining_students
|
||||
else:
|
||||
context.class_students = class_students
|
||||
|
||||
students = [student.student for student in class_students]
|
||||
context.is_student = frappe.session.user in students
|
||||
|
||||
context.live_classes = frappe.get_all(
|
||||
"LMS Live Class",
|
||||
{"class_name": class_name, "date": [">=", getdate()]},
|
||||
@@ -70,7 +59,16 @@ def get_context(context):
|
||||
order_by="date",
|
||||
)
|
||||
|
||||
context.class_courses = get_class_course_details(class_courses)
|
||||
context.all_courses = frappe.get_list(
|
||||
"LMS Course", fields=["name", "title"], limit_page_length=0
|
||||
)
|
||||
context.course_name_list = [course.course for course in context.class_courses]
|
||||
context.assessments = get_assessments(class_name)
|
||||
context.class_students = get_class_student_details(
|
||||
class_students, class_courses, context.assessments
|
||||
)
|
||||
context.is_student = is_student(class_students)
|
||||
context.all_assignments = get_all_assignments(class_name)
|
||||
context.all_quizzes = get_all_quizzes(class_name)
|
||||
|
||||
@@ -103,3 +101,85 @@ def get_all_assignments(class_name):
|
||||
}
|
||||
)
|
||||
return all_assignments
|
||||
|
||||
|
||||
def get_class_course_details(class_courses):
|
||||
for course in class_courses:
|
||||
details = frappe.db.get_value(
|
||||
"LMS Course",
|
||||
course.course,
|
||||
[
|
||||
"name",
|
||||
"title",
|
||||
"image",
|
||||
"upcoming",
|
||||
"short_introduction",
|
||||
"image",
|
||||
"paid_certificate",
|
||||
"price_certificate",
|
||||
"enable_certification",
|
||||
"currency",
|
||||
],
|
||||
as_dict=True,
|
||||
)
|
||||
course.update(details)
|
||||
return class_courses
|
||||
|
||||
|
||||
def get_class_student_details(class_students, class_courses, assessments):
|
||||
for student in class_students:
|
||||
student.update(
|
||||
frappe.db.get_value(
|
||||
"User", student.student, ["name", "full_name", "username", "headline"], as_dict=1
|
||||
)
|
||||
)
|
||||
student.update(frappe.db.get_value("User", student.student, "last_active", as_dict=1))
|
||||
|
||||
courses_completed = 0
|
||||
for course in class_courses:
|
||||
if get_course_progress(course.course, student.student) == 100:
|
||||
courses_completed += 1
|
||||
student["courses_completed"] = courses_completed
|
||||
|
||||
assessments_completed = 0
|
||||
assessments_graded = 0
|
||||
for assessment in assessments:
|
||||
submission = has_submitted_assessment(
|
||||
assessment.assessment_name, assessment.assessment_type, student.student
|
||||
)
|
||||
if submission:
|
||||
assessments_completed += 1
|
||||
|
||||
if (
|
||||
assessment.assessment_type == "LMS Assignment"
|
||||
and has_graded_assessment(submission)
|
||||
):
|
||||
assessments_graded += 1
|
||||
elif assessment.assessment_type == "LMS Quiz":
|
||||
assessments_graded += 1
|
||||
|
||||
student["assessments_completed"] = assessments_completed
|
||||
student["assessments_graded"] = assessments_graded
|
||||
|
||||
return sort_students(class_students)
|
||||
|
||||
|
||||
def sort_students(class_students):
|
||||
session_user = []
|
||||
remaining_students = []
|
||||
|
||||
for student in class_students:
|
||||
if student.student == frappe.session.user:
|
||||
session_user.append(student)
|
||||
else:
|
||||
remaining_students.append(student)
|
||||
|
||||
if len(session_user):
|
||||
return session_user + remaining_students
|
||||
else:
|
||||
return class_students
|
||||
|
||||
|
||||
def is_student(class_students):
|
||||
students = [student.student for student in class_students]
|
||||
return frappe.session.user in students
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "lms/templates/lms_base.html" %}
|
||||
{% block title %}
|
||||
{{ _("All Classes") }}
|
||||
{{ _("All Classes") }}
|
||||
{% endblock %}
|
||||
|
||||
{% block page_content %}
|
||||
@@ -93,38 +93,44 @@
|
||||
{% set course_count = frappe.db.count("Class Course", {"parent": class.name}) %}
|
||||
{% set student_count = frappe.db.count("Class Student", {"parent": class.name}) %}
|
||||
|
||||
<div class="lms-card">
|
||||
<div class="common-card-style column-card" style="min-height: 150px;">
|
||||
|
||||
<div class="lms-card-title mb-4">
|
||||
<div class="bold-heading">
|
||||
{{ class.title }}
|
||||
</div>
|
||||
|
||||
<!-- <div class="text-muted small mb-4">
|
||||
{% if course_count %}
|
||||
<span class="mr-3">
|
||||
{{ course_count }} {{ _("Courses") }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if class.description %}
|
||||
<div class="short-introduction">
|
||||
{{ class.description }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if student_count %}
|
||||
<span>
|
||||
{{ student_count }} {{ _("Students") }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div> -->
|
||||
|
||||
<div class="">
|
||||
<div class="mt-auto mb-1">
|
||||
<svg class="icon icon-sm">
|
||||
<use href="#icon-calendar"></use>
|
||||
</svg>
|
||||
<span>
|
||||
{{ frappe.utils.format_date(class.start_date, "long") }} -
|
||||
{{ frappe.utils.format_date(class.start_date, "medium") }} -
|
||||
</span>
|
||||
<span>
|
||||
{{ frappe.utils.format_date(class.end_date, "long") }}
|
||||
{{ frappe.utils.format_date(class.end_date, "medium") }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="mb-1">
|
||||
<svg class="icon icon-md">
|
||||
<use href="#icon-education"></use>
|
||||
</svg>
|
||||
{{ course_count }} {{ _("Courses") }}
|
||||
</div>
|
||||
|
||||
<div class="mb-1">
|
||||
<svg class="icon icon-md">
|
||||
<use href="#icon-users"></use>
|
||||
</svg>
|
||||
{{ student_count }} {{ _("Students") }}
|
||||
</div>
|
||||
|
||||
<a class="stretched-link" href="/classes/{{ class.name }}"></a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
@@ -8,7 +8,15 @@ def get_context(context):
|
||||
context.is_moderator = has_course_moderator_role()
|
||||
classes = frappe.get_all(
|
||||
"LMS Class",
|
||||
fields=["name", "title", "start_date", "end_date", "paid_class", "seat_count"],
|
||||
fields=[
|
||||
"name",
|
||||
"title",
|
||||
"description",
|
||||
"start_date",
|
||||
"end_date",
|
||||
"paid_class",
|
||||
"seat_count",
|
||||
],
|
||||
)
|
||||
|
||||
past_classes, upcoming_classes = [], []
|
||||
|
||||
@@ -68,7 +68,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% for assessment in assessments %}
|
||||
{% set has_access = is_moderator and assessment.submission or frappe.session.user == student.name %}
|
||||
|
||||
@@ -43,6 +43,7 @@ def get_courses():
|
||||
"name",
|
||||
"upcoming",
|
||||
"title",
|
||||
"short_introduction",
|
||||
"image",
|
||||
"enable_certification",
|
||||
"paid_certificate",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "lms/templates/lms_base.html" %}
|
||||
{% block title %}
|
||||
{{ _('Community') }}
|
||||
{{ _('People') }}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
0
lms/www/quiz_submission/__init__.py
Normal file
0
lms/www/quiz_submission/__init__.py
Normal file
Reference in New Issue
Block a user