diff --git a/lms/hooks.py b/lms/hooks.py index a79fc683..15ce3873 100644 --- a/lms/hooks.py +++ b/lms/hooks.py @@ -95,7 +95,8 @@ override_doctype_class = { # Hook on document methods and events doc_events = { - "Discussion Reply": {"after_insert": "lms.lms.utils.create_notification_log"} + "Discussion Reply": {"after_insert": "lms.lms.utils.create_notification_log"}, + "Course Lesson": {"on_update": "lms.lms.doctype.lms_quiz.lms_quiz.update_lesson_info"} } # Scheduled Tasks @@ -137,35 +138,28 @@ website_route_rules = [ {"from_route": "/courses/", "to_route": "courses/course"}, {"from_route": "/courses//", "to_route": "courses/certificate"}, {"from_route": "/courses//learn", "to_route": "batch/learn"}, - { - "from_route": "/courses//learn/.", - "to_route": "batch/learn", - }, + {"from_route": "/courses//learn/.", + "to_route": "batch/learn"}, {"from_route": "/quizzes", "to_route": "batch/quiz_list"}, {"from_route": "/quizzes/", "to_route": "batch/quiz"}, {"from_route": "/classes/", "to_route": "classes/class"}, {"from_route": "/courses//progress", "to_route": "batch/progress"}, {"from_route": "/courses//join", "to_route": "batch/join"}, {"from_route": "/courses//manage", "to_route": "cohorts"}, - {"from_route": "/courses//cohorts/", "to_route": "cohorts/cohort"}, - { - "from_route": "/courses//cohorts//", - "to_route": "cohorts/cohort", - }, - { - "from_route": "/courses//subgroups//", - "to_route": "cohorts/subgroup", - }, - { - "from_route": "/courses//subgroups///", - "to_route": "cohorts/subgroup", - }, - { - "from_route": "/courses//join///", - "to_route": "cohorts/join", - }, + {"from_route": "/courses//cohorts/", + "to_route": "cohorts/cohort"}, + {"from_route": "/courses//cohorts//", + "to_route": "cohorts/cohort"}, + {"from_route": "/courses//subgroups//", + "to_route": "cohorts/subgroup"}, + {"from_route": "/courses//subgroups///", + "to_route": "cohorts/subgroup"}, + {"from_route": "/courses//join///", + "to_route": "cohorts/join"}, {"from_route": "/users", "to_route": "profiles/profile"}, {"from_route": "/jobs/", "to_route": "jobs/job"}, + {"from_route": "/classes//students/", + "to_route": "/classes/progress"} ] website_redirects = [ diff --git a/lms/lms/doctype/class_student/class_student.json b/lms/lms/doctype/class_student/class_student.json index 4791e664..3442dd7c 100644 --- a/lms/lms/doctype/class_student/class_student.json +++ b/lms/lms/doctype/class_student/class_student.json @@ -8,7 +8,8 @@ "engine": "InnoDB", "field_order": [ "student", - "student_name" + "student_name", + "username" ], "fields": [ { @@ -26,12 +27,19 @@ "in_list_view": 1, "label": "Student Name", "read_only": 1 + }, + { + "fetch_from": "student.username", + "fieldname": "username", + "fieldtype": "Data", + "label": "Username", + "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-11-09 16:25:07.819344", + "modified": "2022-11-15 11:13:39.410578", "modified_by": "Administrator", "module": "LMS", "name": "Class Student", diff --git a/lms/lms/doctype/course_lesson/course_lesson.py b/lms/lms/doctype/course_lesson/course_lesson.py index 3a29ee4d..acc3bc7e 100644 --- a/lms/lms/doctype/course_lesson/course_lesson.py +++ b/lms/lms/doctype/course_lesson/course_lesson.py @@ -33,6 +33,7 @@ class CourseLesson(Document): e = frappe.get_doc(doctype_map[section], name) e.lesson = self.name e.index_ = index + e.course = self.course e.save(ignore_permissions=True) index += 1 self.update_orphan_documents(doctype_map[section], documents) @@ -49,6 +50,7 @@ class CourseLesson(Document): for name in orphan_documents: ex = frappe.get_doc(doctype, name) ex.lesson = None + ex.course = None ex.index_ = 0 ex.index_label = "" ex.save() diff --git a/lms/lms/doctype/lms_class/lms_class.py b/lms/lms/doctype/lms_class/lms_class.py index 58b26443..d7495aff 100644 --- a/lms/lms/doctype/lms_class/lms_class.py +++ b/lms/lms/doctype/lms_class/lms_class.py @@ -7,7 +7,21 @@ from frappe import _ from frappe.utils import cint class LMSClass(Document): - pass + + def validate(self): + validate_membership(self) + + +def validate_membership(self): + for course in self.courses: + for student in self.students: + filters = { + "doctype": "LMS Batch Membership", + "member": student.student, + "course": course.course + } + if not frappe.db.exists(filters): + frappe.get_doc(filters).save() @frappe.whitelist() diff --git a/lms/lms/doctype/lms_quiz/lms_quiz.json b/lms/lms/doctype/lms_quiz/lms_quiz.json index 99ffc184..a3221040 100644 --- a/lms/lms/doctype/lms_quiz/lms_quiz.json +++ b/lms/lms/doctype/lms_quiz/lms_quiz.json @@ -9,9 +9,12 @@ "field_order": [ "title", "questions", - "lesson", + "section_break_3", "max_attempts", - "time" + "time", + "column_break_5", + "lesson", + "course" ], "fields": [ { @@ -46,11 +49,27 @@ "fieldname": "time", "fieldtype": "Int", "label": "Time Per Question (in Seconds)" + }, + { + "fetch_from": "lesson.course", + "fieldname": "course", + "fieldtype": "Link", + "label": "Course", + "options": "LMS Course", + "read_only": 1 + }, + { + "fieldname": "column_break_5", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_3", + "fieldtype": "Section Break" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2022-08-19 17:54:41.685182", + "modified": "2022-11-15 15:36:39.585488", "modified_by": "Administrator", "module": "LMS", "name": "LMS Quiz", @@ -69,8 +88,10 @@ "write": 1 } ], + "show_title_field_in_link": 1, "sort_field": "modified", "sort_order": "DESC", "states": [], + "title_field": "title", "track_changes": 1 } \ No newline at end of file diff --git a/lms/lms/doctype/lms_quiz/lms_quiz.py b/lms/lms/doctype/lms_quiz/lms_quiz.py index 6879c773..539eb33d 100644 --- a/lms/lms/doctype/lms_quiz/lms_quiz.py +++ b/lms/lms/doctype/lms_quiz/lms_quiz.py @@ -60,6 +60,13 @@ class LMSQuiz(Document): return result[0] +def update_lesson_info(doc, method): + if doc.quiz_id: + frappe.db.set_value("LMS Quiz", doc.quiz_id, { + "lesson": doc.name, + "course": doc.course + }) + @frappe.whitelist() def quiz_summary(quiz, results): score = 0 diff --git a/lms/lms/doctype/lms_quiz_submission/lms_quiz_submission.json b/lms/lms/doctype/lms_quiz_submission/lms_quiz_submission.json index a2b13222..80ca9ffd 100644 --- a/lms/lms/doctype/lms_quiz_submission/lms_quiz_submission.json +++ b/lms/lms/doctype/lms_quiz_submission/lms_quiz_submission.json @@ -5,11 +5,12 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "member", - "member_name", - "column_break_3", "quiz", "score", + "course", + "column_break_3", + "member", + "member_name", "section_break_6", "result" ], @@ -46,7 +47,8 @@ "fetch_from": "member.full_name", "fieldname": "member_name", "fieldtype": "Data", - "label": "Member Name" + "label": "Member Name", + "read_only": 1 }, { "fieldname": "column_break_3", @@ -55,12 +57,20 @@ { "fieldname": "section_break_6", "fieldtype": "Section Break" + }, + { + "fetch_from": "quiz.course", + "fieldname": "course", + "fieldtype": "Link", + "label": "Course", + "options": "LMS Course", + "read_only": 1 } ], "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2022-10-10 18:57:42.813738", + "modified": "2022-11-15 15:27:07.770945", "modified_by": "Administrator", "module": "LMS", "name": "LMS Quiz Submission", diff --git a/lms/public/css/style.css b/lms/public/css/style.css index 7ec1ff67..94c7ead0 100644 --- a/lms/public/css/style.css +++ b/lms/public/css/style.css @@ -87,9 +87,9 @@ input[type=checkbox] { } .common-page-style { - padding: 2rem 0 5rem; - padding-top: 3rem; - background-color: var(--bg-color); + padding: 2rem 0 5rem; + padding-top: 3rem; + background-color: var(--bg-color); } .common-card-style { @@ -443,10 +443,6 @@ input[type=checkbox] { padding: 2rem 1rem; } -.member-card .talk-title { - font-weight: bold; -} - .break { flex-basis: 100%; flex-grow: 1; @@ -978,10 +974,6 @@ pre { } } -.section-heading { - font-size: var(--text-4xl); -} - .testimonial-card { flex-direction: column; padding: 2rem; @@ -1835,8 +1827,28 @@ select { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); grid-gap: 1rem; + font-size: var(--text-base); } .class-cours { cursor: pointer; } + +.subheading { + font-weight: 500; + color: var(--gray-900); +} + +.progress-course-header { + display: flex; + justify-content: space-between; + background-color: var(--gray-100); + padding: 0.5rem; + border-radius: var(--border-radius-sm); +} + +.section-heading { + font-size: 1rem; + color: var(--gray-900); + font-weight: 500; +} diff --git a/lms/www/classes/class.html b/lms/www/classes/class.html index 5c21eaa4..31f650fe 100644 --- a/lms/www/classes/class.html +++ b/lms/www/classes/class.html @@ -1,6 +1,6 @@ {% extends "templates/base.html" %} {% block title %} - {{ _(class_info.title) }} + {{ _(class_info.title) }} {% endblock %} @@ -33,7 +33,7 @@
{{ class_info.title }}
-
+
{{ class_info.description }}
@@ -108,11 +108,12 @@ {% if class_students | length %}
{% for student in class_students %} -
+
{{ student.student_name }} +
{% if not loop.last %}
{% endif %} {% endfor %} diff --git a/lms/www/classes/class.py b/lms/www/classes/class.py index 7949f919..2ccc3c77 100644 --- a/lms/www/classes/class.py +++ b/lms/www/classes/class.py @@ -1,8 +1,18 @@ import frappe +from lms.lms.utils import has_course_moderator_role +from frappe import _ def get_context(context): context.no_cache = 1 + + if not has_course_moderator_role(): + message = "Only Moderators have access to this page." + if frappe.session.user == "Guest": + message = "Please login to access this page." + + raise frappe.PermissionError(_(message)) + class_name = frappe.form_dict["classname"] context.class_info = frappe.db.get_value("LMS Class", class_name, ["name", "title", "start_date", "end_date", "description"], as_dict=True) @@ -14,4 +24,4 @@ def get_context(context): context.class_students = frappe.get_all("Class Student", { "parent": class_name - }, ["student", "student_name"]) + }, ["student", "student_name", "username"]) diff --git a/lms/www/classes/index.py b/lms/www/classes/index.py index 89108673..0f0c030f 100644 --- a/lms/www/classes/index.py +++ b/lms/www/classes/index.py @@ -1,5 +1,16 @@ import frappe +from lms.lms.utils import has_course_moderator_role +from frappe import _ + def get_context(context): context.no_cache = 1 + + if not has_course_moderator_role(): + message = "Only Moderators have access to this page." + if frappe.session.user == "Guest": + message = "Please login to access this page." + + raise frappe.PermissionError(_(message)) + context.classes = frappe.get_all("LMS Class", fields=["name", "title", "start_date", "end_date"]) diff --git a/lms/www/classes/progress.html b/lms/www/classes/progress.html new file mode 100644 index 00000000..78908e2d --- /dev/null +++ b/lms/www/classes/progress.html @@ -0,0 +1,76 @@ +{% extends "templates/base.html" %} +{% block title %} + {{ student.first_name }} 's {{ _("Progress") }} +{% endblock %} + + +{% block content %} +
+
+ {{ BreadCrumb(class_info, student) }} +
+
+ {{ student.full_name }} +
+ {{ Progress(class_courses, student) }} +
+
+
+{% endblock %} + + +{% macro BreadCrumb(class_info, student) %} + +{% endmacro %} + + +{% macro Progress(class_info, student) %} +
+ {% for course in class_courses %} +
+
+
{{ course.title }}
+
{{ frappe.utils.cint(course.membership.progress) }}%
+
+ + + {% for quiz in course.quizzes %} + + {% set filters = { "member": student.name, "course": course.course } %} + {% set submitted = frappe.db.exists("LMS Quiz Submission", filters) %} + {% set score = frappe.db.get_value("LMS Quiz Submission", filters, ["score"]) %} + +
+
{{ _("Quiz") }}:
+
+
+ {{ quiz.title }} +
+ {% if submitted %} +
+ {{ score }} +
+ {% else %} +
+ Not Attempted +
+ {% endif %} +
+
+ + {% endfor %} + {% for quiz in class_courses.assignments %} +
+ {{ assignments.assignment }} +
+ {% endfor %} +
+ {% endfor %} +
+{% endmacro %} diff --git a/lms/www/classes/progress.py b/lms/www/classes/progress.py new file mode 100644 index 00000000..d5049332 --- /dev/null +++ b/lms/www/classes/progress.py @@ -0,0 +1,26 @@ +import frappe + + +def get_context(context): + context.no_cache = 1 + + student = frappe.form_dict["username"] + classname = frappe.form_dict["classname"] + + context.student = frappe.db.get_value("User", {"username": student}, ["first_name", "full_name", "name"], as_dict=True) + context.class_info = frappe.db.get_value("LMS Class", classname, ["name"], as_dict=True) + + class_courses = frappe.get_all("Class Course", { + "parent": classname + }, ["course", "title"]) + + for course in class_courses: + course.membership = frappe.db.get_value("LMS Batch Membership", { + "member": context.student.name, + "course": course.course + }, ["progress"], as_dict=True) + course.quizzes = frappe.get_all("LMS Quiz", {"course": course.course}, ["name", "title"]) + course.assignments = frappe.get_all("Lesson Assignment", {"course": course.course}, ["name", "assignment"]) + + print(class_courses) + context.class_courses = class_courses