From 823c79588b30ed11c51f2d728089b85c48659972 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Sun, 19 May 2019 16:01:45 +0530 Subject: [PATCH 01/40] feat: Moving to Jinja --- erpnext/public/js/education/lms/call.js | 2 +- erpnext/www/lms/__init__.py | 0 erpnext/www/lms/all-programs.html | 76 +++++++++++++++ erpnext/www/lms/all_programs.py | 15 +++ erpnext/www/lms/index.html | 118 ++++++++++++++++++++++++ erpnext/www/lms/index.py | 24 +++++ erpnext/www/lms/program.html | 74 +++++++++++++++ erpnext/www/lms/program.py | 15 +++ erpnext/www/{lms.py => lms_legacy.py} | 0 erpnext/www/{lms.html => lms_old.html} | 0 10 files changed, 323 insertions(+), 1 deletion(-) create mode 100644 erpnext/www/lms/__init__.py create mode 100644 erpnext/www/lms/all-programs.html create mode 100644 erpnext/www/lms/all_programs.py create mode 100644 erpnext/www/lms/index.html create mode 100644 erpnext/www/lms/index.py create mode 100644 erpnext/www/lms/program.html create mode 100644 erpnext/www/lms/program.py rename erpnext/www/{lms.py => lms_legacy.py} (100%) rename erpnext/www/{lms.html => lms_old.html} (100%) diff --git a/erpnext/public/js/education/lms/call.js b/erpnext/public/js/education/lms/call.js index e35acbdd75..4edcaaa6d6 100644 --- a/erpnext/public/js/education/lms/call.js +++ b/erpnext/public/js/education/lms/call.js @@ -2,7 +2,7 @@ frappe.ready(() => { frappe.provide('lms'); lms.call = (method, args) => { - const method_path = 'erpnext.www.lms.' + method; + const method_path = 'erpnext.www.lms_legacy.' + method; return new Promise((resolve, reject) => { return frappe.call({ method: method_path, diff --git a/erpnext/www/lms/__init__.py b/erpnext/www/lms/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/www/lms/all-programs.html b/erpnext/www/lms/all-programs.html new file mode 100644 index 0000000000..1003e5e087 --- /dev/null +++ b/erpnext/www/lms/all-programs.html @@ -0,0 +1,76 @@ +{% extends "templates/base.html" %} +{% block title %}All Programs{% endblock %} + +{% block head_include %} + +{% endblock %} + + +{% macro card(program, is_enrolled) %} +
+ +
+{% endmacro %} + +{% block content %} +
+
+

All Programs

+

+ Start Learning +

+
+
+
+ {% for program in all_programs %} + {{ card(program.program, program.is_enrolled) }} + {% endfor %} +
+

+ View All Programs +

+
+
+{% endblock %} \ No newline at end of file diff --git a/erpnext/www/lms/all_programs.py b/erpnext/www/lms/all_programs.py new file mode 100644 index 0000000000..aa10e2b039 --- /dev/null +++ b/erpnext/www/lms/all_programs.py @@ -0,0 +1,15 @@ +from __future__ import unicode_literals +import erpnext.education.utils as utils +import frappe + +no_cache = 1 + +def get_context(context): + context.education_settings = frappe.get_single("Education Settings") + context.all_programs = get_all_programs() + +def get_all_programs(): + program_names = frappe.get_all("Program", filters={"is_published": True}) + if program_names: + program_list = [utils.get_program_and_enrollment_status(program['name']) for program in program_names] + return program_list diff --git a/erpnext/www/lms/index.html b/erpnext/www/lms/index.html new file mode 100644 index 0000000000..f4756993cf --- /dev/null +++ b/erpnext/www/lms/index.html @@ -0,0 +1,118 @@ +{% extends "templates/base.html" %} +{% block title %}{{ education_settings.portal_title }}{% endblock %} + +{% block head_include %} + + + +{% endblock %} + + +{% macro card(program, is_enrolled) %} +
+ +
+{% endmacro %} + +{% block content %} +
+
+

{{ education_settings.portal_title }}

+

{{ education_settings.description }}

+

+ Start Learning +

+ Go to erpnext.com +
+
+
+ {% for program in featured_programs %} + {{ card(program.program, program.is_enrolled) }} + {% endfor %} +
+

+ View All Programs +

+
+
+
+
+
+
+ +
+
Curated Courses
+
Start with a 14 day trial to get instant access to your own ERPNext Instance. Get expert support and world class hosting too.
+
+
+ +
+ +
+
Built by Experts
+
For self hosted users, ERPNext Support provides you the priority support and bug fix guarantee, so you can stop worrying.
+
+
+ +
+ +
+
Learn from the OEMs
+
ERPNext is open source and infinitely extensible. Customize it, build upon it, add your own apps built with Frappe Framework.
+
+
+
+
+
+
+
+

About ERPNext

+

ERPNext is the world's best 100% open source ERP used by over 5000 companies worldwide.

+ +
+
+{% endblock %} \ No newline at end of file diff --git a/erpnext/www/lms/index.py b/erpnext/www/lms/index.py new file mode 100644 index 0000000000..99dcb651aa --- /dev/null +++ b/erpnext/www/lms/index.py @@ -0,0 +1,24 @@ +from __future__ import unicode_literals +import erpnext.education.utils as utils +import frappe + +no_cache = 1 + +def get_context(context): + context.education_settings = frappe.get_single("Education Settings") + context.featured_programs = get_featured_programs() + + +def get_featured_programs(): + featured_program_names = frappe.get_all("Program", filters={"is_published": True, "is_featured": True}) + if featured_program_names: + featured_list = [utils.get_program_and_enrollment_status(program['name']) for program in featured_program_names] + return featured_list + else: + return get_all_programs()[:2] + +def get_all_programs(): + program_names = frappe.get_all("Program", filters={"is_published": True}) + if program_names: + program_list = [utils.get_program_and_enrollment_status(program['name']) for program in program_names] + return program_list diff --git a/erpnext/www/lms/program.html b/erpnext/www/lms/program.html new file mode 100644 index 0000000000..60104e72d1 --- /dev/null +++ b/erpnext/www/lms/program.html @@ -0,0 +1,74 @@ +{% extends "templates/base.html" %} +{% block title %}{{ program.program_name }}{% endblock %} + +{% block head_include %} + +{% endblock %} + + +{% macro card(program, is_enrolled) %} +
+ +
+{% endmacro %} + +{% block content %} +
+
+

{{ program.program_name }}

+

{{ program.description }}

+

+ Sign Up +

+
+
+
+ {% for program in all_programs %} + {{ card(program.program, program.is_enrolled) }} + {% endfor %} +
+
+
+{% endblock %} \ No newline at end of file diff --git a/erpnext/www/lms/program.py b/erpnext/www/lms/program.py new file mode 100644 index 0000000000..f38e65246b --- /dev/null +++ b/erpnext/www/lms/program.py @@ -0,0 +1,15 @@ +from __future__ import unicode_literals +import erpnext.education.utils as utils +import frappe + +no_cache = 1 + +def get_context(context): + context.education_settings = frappe.get_single("Education Settings") + context.program = get_program(frappe.form_dict['name']) + +def get_program(program_name): + try: + return frappe.get_doc('Program', program_name) + except frappe.DoesNotExistError: + frappe.throw(_("Program {0} does not exist.".format(program_name))) \ No newline at end of file diff --git a/erpnext/www/lms.py b/erpnext/www/lms_legacy.py similarity index 100% rename from erpnext/www/lms.py rename to erpnext/www/lms_legacy.py diff --git a/erpnext/www/lms.html b/erpnext/www/lms_old.html similarity index 100% rename from erpnext/www/lms.html rename to erpnext/www/lms_old.html From d7e8298a5f58e200a393a541d1210ff474f00632 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 20 May 2019 11:55:17 +0530 Subject: [PATCH 02/40] feat: added program and course pages --- erpnext/www/lms/course.html | 77 ++++++++++++++++++++++++++++++++++++ erpnext/www/lms/course.py | 11 ++++++ erpnext/www/lms/program.html | 25 ++++++------ 3 files changed, 99 insertions(+), 14 deletions(-) create mode 100644 erpnext/www/lms/course.html create mode 100644 erpnext/www/lms/course.py diff --git a/erpnext/www/lms/course.html b/erpnext/www/lms/course.html new file mode 100644 index 0000000000..2e414a0369 --- /dev/null +++ b/erpnext/www/lms/course.html @@ -0,0 +1,77 @@ +{% extends "templates/base.html" %} +{% block title %}{{ course.course_name }}{% endblock %} + +{% block head_include %} + +{% endblock %} + + +{% macro card(topic, index, length) %} + +{% endmacro %} + +{% block content %} +
+
+

{{ course.course_name }}

+

{{ course.course_intro }}

+

+ Sign Up +

+
+
+
+ {% for topic in topics %} + {{ card(topic.as_dict(), loop.index, topics|length) }} + {% endfor %} +
+
+
+{% endblock %} \ No newline at end of file diff --git a/erpnext/www/lms/course.py b/erpnext/www/lms/course.py new file mode 100644 index 0000000000..d8670e6977 --- /dev/null +++ b/erpnext/www/lms/course.py @@ -0,0 +1,11 @@ +from __future__ import unicode_literals +import erpnext.education.utils as utils +import frappe + +no_cache = 1 + +def get_context(context): + context.education_settings = frappe.get_single("Education Settings") + course = frappe.get_doc('Course', frappe.form_dict['name']) + context.course = course + context.topics = course.get_topics() \ No newline at end of file diff --git a/erpnext/www/lms/program.html b/erpnext/www/lms/program.html index 60104e72d1..35f7f8a69c 100644 --- a/erpnext/www/lms/program.html +++ b/erpnext/www/lms/program.html @@ -31,23 +31,20 @@ {% endblock %} -{% macro card(program, is_enrolled) %} -
-
- - {% if program.hero_image %} -
+{% macro card(course, index, length) %} +
- {% for program in all_programs %} - {{ card(program.program, program.is_enrolled) }} + {% for course in program.courses %} + {{ card(frappe.get_doc("Course", course.course), loop.index, program.courses|length) }} {% endfor %}
From c8c790a097e7b65176dcf0682c49998318886e8e Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 21 May 2019 12:04:50 +0530 Subject: [PATCH 03/40] feat: Added content page and minor doctype changes --- erpnext/education/doctype/course/course.json | 601 ++++--------------- erpnext/education/doctype/video/video.json | 214 +------ erpnext/www/lms/content.html | 136 +++++ erpnext/www/lms/content.py | 39 ++ 4 files changed, 313 insertions(+), 677 deletions(-) create mode 100644 erpnext/www/lms/content.html create mode 100644 erpnext/www/lms/content.py diff --git a/erpnext/education/doctype/course/course.json b/erpnext/education/doctype/course/course.json index 072e8b4afb..234b29d421 100644 --- a/erpnext/education/doctype/course/course.json +++ b/erpnext/education/doctype/course/course.json @@ -1,514 +1,135 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:course_code", - "beta": 0, - "creation": "2015-09-07 12:39:55.181893", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 0, - "engine": "InnoDB", + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:course_code", + "creation": "2015-09-07 12:39:55.181893", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "course_name", + "department", + "parent_course", + "column_break_3", + "course_code", + "course_abbreviation", + "section_break_6", + "topics", + "course_intro", + "hero_image", + "assessment", + "default_grading_scale", + "assessment_criteria" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "course_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Course Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "course_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Course Name", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "department", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Department", - "length": 0, - "no_copy": 0, - "options": "Department", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "department", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Department", + "options": "Department" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "parent_course", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Parent Course (Leave blank, if this isn't part of Parent Course)", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "parent_course", + "fieldtype": "Data", + "label": "Parent Course (Leave blank, if this isn't part of Parent Course)" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "course_code", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Course Code", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "fieldname": "course_code", + "fieldtype": "Data", + "label": "Course Code", + "reqd": 1, "unique": 1 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "course_abbreviation", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Course Abbreviation", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "course_abbreviation", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Course Abbreviation" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "section_break_6", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_6", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "topics", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Topics", - "length": 0, - "no_copy": 0, - "options": "Course Topic", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "topics", + "fieldtype": "Table", + "label": "Topics", + "options": "Course Topic" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "course_intro", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Course Intro", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "course_intro", + "fieldtype": "Small Text", + "label": "Course Intro" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "hero_image", - "fieldtype": "Attach Image", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Hero Image", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "hero_image", + "fieldtype": "Attach Image", + "label": "Hero Image" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "assessment", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Assessment", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "assessment", + "fieldtype": "Section Break", + "label": "Assessment" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "default_grading_scale", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Default Grading Scale", - "length": 0, - "no_copy": 0, - "options": "Grading Scale", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "default_grading_scale", + "fieldtype": "Link", + "label": "Default Grading Scale", + "options": "Grading Scale" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "assessment_criteria", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Assessment Criteria", - "length": 0, - "no_copy": 0, - "options": "Course Assessment Criteria", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "assessment_criteria", + "fieldtype": "Table", + "label": "Assessment Criteria", + "options": "Course Assessment Criteria" } - ], - "has_web_view": 0, - "hide_toolbar": 0, - "idx": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "menu_index": 0, - "modified": "2019-04-09 11:35:27.354877", - "modified_by": "Administrator", - "module": "Education", - "name": "Course", - "name_case": "", - "owner": "Administrator", + ], + "modified": "2019-05-20 19:27:09.381482", + "modified_by": "Administrator", + "module": "Education", + "name": "Course", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Academics User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Academics User", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Instructor", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Instructor", + "share": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "restrict_to_domain": "Education", - "search_fields": "course_name", - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "restrict_to_domain": "Education", + "search_fields": "course_name", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/education/doctype/video/video.json b/erpnext/education/doctype/video/video.json index cc8f718ba4..3d11bd256f 100644 --- a/erpnext/education/doctype/video/video.json +++ b/erpnext/education/doctype/video/video.json @@ -1,262 +1,102 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, "allow_import": 1, - "allow_rename": 0, "autoname": "field:title", - "beta": 0, "creation": "2018-10-17 05:47:13.087395", - "custom": 0, - "docstatus": 0, "doctype": "DocType", - "document_type": "", "editable_grid": 1, "engine": "InnoDB", + "field_order": [ + "title", + "description", + "duration", + "provider", + "url", + "publish_date" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "title", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, + "in_list_view": 1, "label": "Title", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "reqd": 1, "unique": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "description", "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, + "in_list_view": 1, "label": "Description", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "duration", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Duration", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Duration" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "url", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, + "in_list_view": 1, "label": "URL", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "publish_date", "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Publish Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Publish Date" + }, + { + "fieldname": "provider", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Provider", + "options": "YouTube\nVimeo", + "reqd": 1 } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-11-25 19:07:17.134288", + "modified": "2019-05-20 15:11:53.075093", "modified_by": "Administrator", "module": "Education", "name": "Video", - "name_case": "", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Academics User", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Instructor", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "LMS User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 + "share": 1 } ], "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/www/lms/content.html b/erpnext/www/lms/content.html new file mode 100644 index 0000000000..3e00c79f9c --- /dev/null +++ b/erpnext/www/lms/content.html @@ -0,0 +1,136 @@ +{% extends "templates/base.html" %} +{% block title %}{% endblock %} + +{% block head_include %} + + +{% endblock %} + +{% macro navigation() %} +
+
+

{{ content.name }}

+
+
+ {% if previous %} + Previous + {% else %} + Back to Course + {% endif %} + + {% if next %} + + {% else %} + + {% endif %} +
+
+{% endmacro %} + +{% macro video() %} +
+ {{ navigation() }} +
+ {% if content.duration %} + {{ content.duration }} Mins + {% endif %} + + {% if content.publish_date and content.duration%} + - + {% endif %} + + {% if content.publish_date %} + Published on {{ content.publish_date.strftime('%d, %b %Y') }} + {% endif %} +
+
+
+
+ {{ content.description }} +
+ +{% block script %} + + +{% endblock %} +{% endmacro %} + +{% macro article() %} +
+ {{ navigation() }} +
+ {% if content.author or content.publish_date %} + Published + {% endif %} + {% if content.author %} + by {{ content.author }} + {% endif %} + {% if content.publish_date %} + on {{ content.publish_date.strftime('%d, %b %Y') }} + {% endif %} +
+
+
+ {{ content.content }} +
+{% endmacro %} + +{% block content %} +
+
+
+ {% if content_type=='Video' %} + {{ video() }} + {% elif content_type=='Article'%} + {{ article() }} + {% elif content_type=='Quiz' %} +

Quiz: {{ content.name }}

+ {% endif %} +
+
+
+{% endblock %} \ No newline at end of file diff --git a/erpnext/www/lms/content.py b/erpnext/www/lms/content.py new file mode 100644 index 0000000000..9d26a326d9 --- /dev/null +++ b/erpnext/www/lms/content.py @@ -0,0 +1,39 @@ +from __future__ import unicode_literals +import erpnext.education.utils as utils +import frappe + +no_cache = 1 + +def get_context(context): + context.course = frappe.form_dict['course'] + context.topic = frappe.form_dict['topic'] + content = frappe.form_dict['content'] + context.content_type = frappe.form_dict['type'] + + context.content = frappe.get_doc(context.content_type, content).as_dict() + + context.previous = get_previous_content(context.topic, context.course, context.content, context.content_type) + + context.next = get_next_content(context.topic, context.course, context.content, context.content_type) + +def get_next_content(topic, course, content, content_type): + if frappe.session.user == "Guest": + return None + topic = frappe.get_doc("Topic", topic) + content_list = [{'content_type':item.doctype, 'content':item.name} for item in topic.get_contents()] + current_index = content_list.index({'content': content.name, 'content_type': content_type}) + try: + return content_list[current_index + 1] + except IndexError: + return None + +def get_previous_content(topic, course, content, content_type): + if frappe.session.user == "Guest": + return None + topic = frappe.get_doc("Topic", topic) + content_list = [{'content_type':item.doctype, 'content':item.name} for item in topic.get_contents()] + current_index = content_list.index({'content': content.name, 'content_type': content_type}) + if current_index == 0: + return None + else: + return content_list[current_index - 1] \ No newline at end of file From 87a7438d3821351a15ddee97a51d8a677e9f2d0a Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 21 May 2019 12:05:19 +0530 Subject: [PATCH 04/40] chore: added macro for page hero --- erpnext/www/lms/course.html | 24 ++++++++++++++---------- erpnext/www/lms/macros/hero.html | 20 ++++++++++++++++++++ erpnext/www/lms/program.html | 13 ++++--------- 3 files changed, 38 insertions(+), 19 deletions(-) create mode 100644 erpnext/www/lms/macros/hero.html diff --git a/erpnext/www/lms/course.html b/erpnext/www/lms/course.html index 2e414a0369..68128dca1d 100644 --- a/erpnext/www/lms/course.html +++ b/erpnext/www/lms/course.html @@ -1,5 +1,6 @@ {% extends "templates/base.html" %} {% block title %}{{ course.course_name }}{% endblock %} +{% from "www/lms/macros/hero.html" import hero %} {% block head_include %} {% endblock %} - {% macro card(program, is_enrolled) %} -
-
- - {% if program.hero_image %} -
- {% else %} -
-
{{ program.program_name }}
-
- {% endif %} -
-
{{ program.program_name }}
-
{{ program.description }}
-
-
- + {% endmacro %} From 08425d46fa114c0cb1787c9d558faa923c2f736d Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 29 May 2019 15:42:57 +0530 Subject: [PATCH 09/40] refactor: set Student as default portal role for Education --- erpnext/domains/education.py | 2 +- erpnext/education/doctype/student/student.py | 2 +- erpnext/education/setup.py | 22 +++++++++++++++++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/erpnext/domains/education.py b/erpnext/domains/education.py index 55e4eed801..bbaa6e55d9 100644 --- a/erpnext/domains/education.py +++ b/erpnext/domains/education.py @@ -14,7 +14,7 @@ data = { 'Student Attendance Tool', 'Student Applicant' ], - 'default_portal_role': 'LMS User', + 'default_portal_role': 'Student', 'restricted_roles': [ 'Student', 'Instructor', diff --git a/erpnext/education/doctype/student/student.py b/erpnext/education/doctype/student/student.py index 529f78d4f5..da25880c81 100644 --- a/erpnext/education/doctype/student/student.py +++ b/erpnext/education/doctype/student/student.py @@ -54,7 +54,7 @@ class Student(Document): 'send_welcome_email': 1, 'user_type': 'Website User' }) - student_user.add_roles("Student", "LMS User") + student_user.add_roles("Student") student_user.save() update_password_link = student_user.reset_password() diff --git a/erpnext/education/setup.py b/erpnext/education/setup.py index ed1d69e80d..9d16ebeea8 100644 --- a/erpnext/education/setup.py +++ b/erpnext/education/setup.py @@ -9,7 +9,8 @@ from erpnext.setup.utils import insert_record def setup_education(): - if frappe.db.exists('Academic Year', '2015-16'): + disable_desk_access_for_student_role() + if frappe.db.exists("Academic Year", "2015-16"): # already setup return create_academic_sessions() @@ -26,3 +27,22 @@ def create_academic_sessions(): {"doctype": "Academic Term", "academic_year": "2017-18", "term_name": "Semester 2"} ] insert_record(data) + +def disable_desk_access_for_student_role(): + try: + student_role = frappe.get_doc("Role", "Student") + except frappe.DoesNotExistError: + create_student_role() + return + + student_role.desk_access = 0 + student_role.save() + +def create_student_role(): + student_role = frappe.get_doc({ + "doctype": "Role", + "role_name": "Student", + "desk_access": 0, + "restrict_to_domain": "Education" + }) + student.insert() From 5aa8df840e4a9f324a14077583ef9f0d10b718dd Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 29 May 2019 18:38:09 +0530 Subject: [PATCH 10/40] refactor: cards and hero markup --- erpnext/www/lms/all-programs.html | 39 ++++++++++++++----------------- erpnext/www/lms/index.html | 6 ++--- erpnext/www/lms/macros/hero.html | 15 ++++++------ 3 files changed, 28 insertions(+), 32 deletions(-) diff --git a/erpnext/www/lms/all-programs.html b/erpnext/www/lms/all-programs.html index 1003e5e087..f5b7595ec8 100644 --- a/erpnext/www/lms/all-programs.html +++ b/erpnext/www/lms/all-programs.html @@ -32,25 +32,25 @@ {% macro card(program, is_enrolled) %} - {% endblock %} \ No newline at end of file diff --git a/erpnext/www/lms/index.html b/erpnext/www/lms/index.html index 54eb7c48fb..ffa46e123c 100644 --- a/erpnext/www/lms/index.html +++ b/erpnext/www/lms/index.html @@ -32,8 +32,8 @@ {% endblock %} -{% macro card(program, is_enrolled) %} -
+{% macro card(program) %} +
{% if program.hero_image %} @@ -68,7 +68,7 @@
{% for program in featured_programs %} - {{ card(program.program, program.is_enrolled) }} + {{ card(program.program) }} {% endfor %}

diff --git a/erpnext/www/lms/macros/hero.html b/erpnext/www/lms/macros/hero.html index f837b5c447..8c09580b8f 100644 --- a/erpnext/www/lms/macros/hero.html +++ b/erpnext/www/lms/macros/hero.html @@ -1,22 +1,21 @@ -{% macro hero(title, description) %} +{% macro hero(title, description, is_enrolled) %}

{% block script %} {% endblock %} {% endmacro %} \ No newline at end of file From f927502cc5f460fb716f4e861d908c307f3e3881 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 29 May 2019 18:39:52 +0530 Subject: [PATCH 11/40] refactor: added lms utilities for enrollment and program list --- erpnext/education/utils.py | 125 ++++++++++++++++++++++++-------- erpnext/www/lms/all_programs.py | 8 +- erpnext/www/lms/index.py | 13 +--- erpnext/www/lms/program.py | 1 + 4 files changed, 96 insertions(+), 51 deletions(-) diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py index bf766adc09..398a2a5ea7 100644 --- a/erpnext/education/utils.py +++ b/erpnext/education/utils.py @@ -57,9 +57,10 @@ def validate_duplicate_student(students): # LMS Utils def get_current_student(): - """ - Returns student user name, example EDU-STU-2018-00001 (Based on the naming series). - Takes email from from frappe.session.user + """Returns current student from frappe.session.user + + Returns: + object: Student Document """ email = frappe.session.user if email in ('Administrator', 'Guest'): @@ -70,41 +71,101 @@ def get_current_student(): except (IndexError, frappe.DoesNotExistError): return None -def check_super_access(): - current_user = frappe.get_doc('User', frappe.session.user) - roles = set([role.role for role in current_user.roles]) - return bool(roles & {'Administrator', 'Instructor', 'Education Manager', 'System Manager', 'Academic User'}) +def get_portal_programs(): + """Returns a list of all program to be displayed on the portal + Programs are returned based on the following logic + is_published and (student_is_enrolled or student_can_self_enroll) -def get_program_enrollment(program_name): + Returns: + list of dict: List of all programs and to be displayed on the portal along with enrollment status """ - Function to get program enrollments for a particular student for a program - """ - student = get_current_student() - if not student: + published_programs = frappe.get_all("Program", filters={"is_published": True}) + if not published_programs: return None + + program_list = [frappe.get_doc("Program", program) for program in published_programs] + portal_programs = [] + + for program in program_list: + enrollment_status = get_enrollment_status(program.name) + if enrollment_status or program.allow_self_enroll: + portal_programs.append({'program': program, 'is_enrolled': enrollment_status}) + + return portal_programs + +def get_enrollment_status(program, student=None): + """Returns enrollment status for current student + + Args: + program (string): Name of the program + student (object): instance of Student document + + Returns: + bool: Is current user enrolled or not + """ + if not student: + student = get_current_student() + if student and get_enrollment('program', program, student.name): + return True else: - enrollment = frappe.get_all("Program Enrollment", filters={'student':student.name, 'program': program_name}) - if enrollment: - return enrollment[0].name - else: - return None + return False -def get_program_and_enrollment_status(program_name): - program = frappe.get_doc('Program', program_name) - is_enrolled = bool(get_program_enrollment(program_name)) or check_super_access() - return {'program': program, 'is_enrolled': is_enrolled} +def get_enrollment(master, document, student): + """Gets enrollment for course or program -def get_course_enrollment(course_name): - student = get_current_student() - if not student: - return None - enrollment_name = frappe.get_all("Course Enrollment", filters={'student': student.name, 'course':course_name}) - try: - name = enrollment_name[0].name - enrollment = frappe.get_doc("Course Enrollment", name) - return enrollment - except: - return None + Args: + master (string): can either be program or course + document (string): program or course name + student (string): Student ID + + Returns: + string: Enrollment Name if exists else returns empty string + """ + if master == 'program': + enrollments = frappe.get_all("Program Enrollment", filters={'student':student, 'program': document}) + if master == 'course': + enrollments = frappe.get_all("Course Enrollment", filters={'student':student, 'course': document}) + + if enrollments: + return enrollment[0].name + else: + return '' + +# def check_super_access(): +# current_user = frappe.get_doc('User', frappe.session.user) +# roles = set([role.role for role in current_user.roles]) +# return bool(roles & {'Administrator', 'Instructor', 'Education Manager', 'System Manager', 'Academic User'}) + +# def get_program_enrollment(program_name): +# """ +# Function to get program enrollments for a particular student for a program +# """ +# student = get_current_student() +# if not student: +# return None +# else: +# enrollment = frappe.get_all("Program Enrollment", filters={'student':student.name, 'program': program_name}) +# if enrollment: +# return enrollment[0].name +# else: +# return None + +# def get_program_and_enrollment_status(program_name): +# program = frappe.get_doc('Program', program_name) +# is_enrolled = bool(get_program_enrollment(program_name)) or check_super_access() +# return {'program': program, 'is_enrolled': is_enrolled} + +# def get_course_enrollment(course_name): +# student = get_current_student() +# if not student: +# return None +# enrollment_name = frappe.get_all("Course Enrollment", filters={'student': student.name, 'course':course_name}) +# try: +# name = enrollment_name[0].name +# enrollment = frappe.get_doc("Course Enrollment", name) +# return enrollment +# except: +# return None def create_student_from_current_user(): user = frappe.get_doc("User", frappe.session.user) diff --git a/erpnext/www/lms/all_programs.py b/erpnext/www/lms/all_programs.py index aa10e2b039..c0c18c3534 100644 --- a/erpnext/www/lms/all_programs.py +++ b/erpnext/www/lms/all_programs.py @@ -6,10 +6,4 @@ no_cache = 1 def get_context(context): context.education_settings = frappe.get_single("Education Settings") - context.all_programs = get_all_programs() - -def get_all_programs(): - program_names = frappe.get_all("Program", filters={"is_published": True}) - if program_names: - program_list = [utils.get_program_and_enrollment_status(program['name']) for program in program_names] - return program_list + context.all_programs = utils.get_portal_programs() \ No newline at end of file diff --git a/erpnext/www/lms/index.py b/erpnext/www/lms/index.py index 15ca0b4628..00f66e72c3 100644 --- a/erpnext/www/lms/index.py +++ b/erpnext/www/lms/index.py @@ -13,15 +13,4 @@ def get_context(context): def get_featured_programs(): - featured_program_names = frappe.get_all("Program", filters={"is_published": True, "is_featured": True}) - if featured_program_names: - featured_list = [utils.get_program_and_enrollment_status(program['name']) for program in featured_program_names] - return featured_list - else: - return get_all_programs()[:2] - -def get_all_programs(): - program_names = frappe.get_all("Program", filters={"is_published": True}) - if program_names: - program_list = [utils.get_program_and_enrollment_status(program['name']) for program in program_names] - return program_list + return utils.get_portal_programs() \ No newline at end of file diff --git a/erpnext/www/lms/program.py b/erpnext/www/lms/program.py index f38e65246b..827b11a4ab 100644 --- a/erpnext/www/lms/program.py +++ b/erpnext/www/lms/program.py @@ -7,6 +7,7 @@ no_cache = 1 def get_context(context): context.education_settings = frappe.get_single("Education Settings") context.program = get_program(frappe.form_dict['name']) + context.is_enrolled = utils.get_enrollment_status(program) def get_program(program_name): try: From f22793f91b31ad1e7c3e038a7001f5c57bc2c1cd Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 30 May 2019 16:34:53 +0530 Subject: [PATCH 12/40] refactor: added program card macro --- erpnext/www/lms/all-programs.html | 29 +++-------------------------- erpnext/www/lms/index.html | 28 +++------------------------- erpnext/www/lms/macros/card.html | 22 ++++++++++++++++++++++ 3 files changed, 28 insertions(+), 51 deletions(-) create mode 100644 erpnext/www/lms/macros/card.html diff --git a/erpnext/www/lms/all-programs.html b/erpnext/www/lms/all-programs.html index f5b7595ec8..088498b4a9 100644 --- a/erpnext/www/lms/all-programs.html +++ b/erpnext/www/lms/all-programs.html @@ -1,5 +1,6 @@ {% extends "templates/base.html" %} {% block title %}All Programs{% endblock %} +{% from "www/lms/macros/card.html" import program_card %} {% block head_include %} {% endblock %} - -{% macro card(program, is_enrolled) %} - -{% endmacro %} - {% block content %}
{% for program in all_programs %} - {{ card(program.program, program.is_enrolled) }} + {{ program_card(program) }} {% endfor %}
diff --git a/erpnext/www/lms/index.html b/erpnext/www/lms/index.html index ffa46e123c..ba3034c79e 100644 --- a/erpnext/www/lms/index.html +++ b/erpnext/www/lms/index.html @@ -1,5 +1,6 @@ {% extends "templates/base.html" %} {% block title %}{{ education_settings.portal_title }}{% endblock %} +{% from "www/lms/macros/card.html" import program_card %} {% block head_include %} @@ -32,43 +33,20 @@ {% endblock %} -{% macro card(program) %} - -{% endmacro %} - {% block content %}

{{ education_settings.portal_title }}

{{ education_settings.description }}

- Start Learning + {{ 'Start Learning' if frappe.session.user == 'Guest' else 'Explore Programs'}}

Go to erpnext.com
{% for program in featured_programs %} - {{ card(program.program) }} + {{ program_card(program) }} {% endfor %}

diff --git a/erpnext/www/lms/macros/card.html b/erpnext/www/lms/macros/card.html new file mode 100644 index 0000000000..8cf8a78e56 --- /dev/null +++ b/erpnext/www/lms/macros/card.html @@ -0,0 +1,22 @@ +{% macro program_card(program) %} +

+{% endmacro %} \ No newline at end of file From dfdb92f4b1e086d76d02a0151d51e28358742224 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 30 May 2019 16:35:15 +0530 Subject: [PATCH 13/40] refactor: added enrollment api --- erpnext/education/utils.py | 88 +++++++++++++++++++++++--------- erpnext/www/lms/macros/hero.html | 40 +++++++++++++-- 2 files changed, 99 insertions(+), 29 deletions(-) diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py index 398a2a5ea7..09ac22ca27 100644 --- a/erpnext/education/utils.py +++ b/erpnext/education/utils.py @@ -60,7 +60,7 @@ def get_current_student(): """Returns current student from frappe.session.user Returns: - object: Student Document + object: Student Document """ email = frappe.session.user if email in ('Administrator', 'Guest'): @@ -77,32 +77,29 @@ def get_portal_programs(): is_published and (student_is_enrolled or student_can_self_enroll) Returns: - list of dict: List of all programs and to be displayed on the portal along with enrollment status + list of objects: List of all programs and to be displayed on the portal along with enrollment status """ published_programs = frappe.get_all("Program", filters={"is_published": True}) if not published_programs: return None program_list = [frappe.get_doc("Program", program) for program in published_programs] - portal_programs = [] - - for program in program_list: - enrollment_status = get_enrollment_status(program.name) - if enrollment_status or program.allow_self_enroll: - portal_programs.append({'program': program, 'is_enrolled': enrollment_status}) + portal_programs = [program for program in program_list if allowed_program_access(program.name) or program.allow_self_enroll] return portal_programs -def get_enrollment_status(program, student=None): +def allowed_program_access(program, student=None): """Returns enrollment status for current student Args: - program (string): Name of the program - student (object): instance of Student document + program (string): Name of the program + student (object): instance of Student document Returns: - bool: Is current user enrolled or not + bool: Is current user enrolled or not """ + if has_super_access(): + return True if not student: student = get_current_student() if student and get_enrollment('program', program, student.name): @@ -114,33 +111,74 @@ def get_enrollment(master, document, student): """Gets enrollment for course or program Args: - master (string): can either be program or course - document (string): program or course name - student (string): Student ID + master (string): can either be program or course + document (string): program or course name + student (string): Student ID Returns: - string: Enrollment Name if exists else returns empty string + string: Enrollment Name if exists else returns empty string """ if master == 'program': - enrollments = frappe.get_all("Program Enrollment", filters={'student':student, 'program': document}) + enrollments = frappe.get_all("Program Enrollment", filters={'student':student, 'program': document, 'docstatus': 1}) if master == 'course': enrollments = frappe.get_all("Course Enrollment", filters={'student':student, 'course': document}) if enrollments: - return enrollment[0].name + return enrollments[0].name else: - return '' + return None -# def check_super_access(): -# current_user = frappe.get_doc('User', frappe.session.user) -# roles = set([role.role for role in current_user.roles]) -# return bool(roles & {'Administrator', 'Instructor', 'Education Manager', 'System Manager', 'Academic User'}) +@frappe.whitelist() +def enroll_in_program(program_name, student=None): + """Enroll student in program + + Args: + program_name (string): Name of the program to be enrolled into + student (string, optional): name of student who has to be enrolled, if not + provided, a student will be created from the current user + + Returns: + string: name of the program enrollment document + """ + if has_super_access(): + return + + if not student == None: + student = frappe.get_doc("Student", student) + else: + # Check if self enrollment in allowed + program = frappe.get_doc('Program', program_name) + if not program.allow_self_enroll: + return frappe.throw("You are not allowed to enroll for this course") + + student = get_current_student() + if not student: + student = create_student_from_current_user() + + # Check if student is already enrolled in program + enrollment = get_enrollment('program', program_name, student.name) + if enrollment: + return enrollment + + # Check if self enrollment in allowed + program = frappe.get_doc('Program', program_name) + if not program.allow_self_enroll: + return frappe.throw("You are not allowed to enroll for this course") + + # Enroll in program + program_enrollment = student.enroll_in_program(program_name) + return program_enrollment.name + +def has_super_access(): + current_user = frappe.get_doc('User', frappe.session.user) + roles = set([role.role for role in current_user.roles]) + return bool(roles & {'Administrator', 'Instructor', 'Education Manager', 'System Manager', 'Academic User'}) # def get_program_enrollment(program_name): # """ # Function to get program enrollments for a particular student for a program # """ -# student = get_current_student() +# student get_current_student() # if not student: # return None # else: @@ -169,6 +207,7 @@ def get_enrollment(master, document, student): def create_student_from_current_user(): user = frappe.get_doc("User", frappe.session.user) + student = frappe.get_doc({ "doctype": "Student", "first_name": user.first_name, @@ -176,6 +215,7 @@ def create_student_from_current_user(): "student_email_id": user.email, "user": frappe.session.user }) + student.save(ignore_permissions=True) return student diff --git a/erpnext/www/lms/macros/hero.html b/erpnext/www/lms/macros/hero.html index 8c09580b8f..89011b2891 100644 --- a/erpnext/www/lms/macros/hero.html +++ b/erpnext/www/lms/macros/hero.html @@ -1,20 +1,50 @@ -{% macro hero(title, description, is_enrolled) %} +{% macro hero(title, description, has_access) %}
-

{{ title}}

+

{{ title }}

{{ description }}

{% if frappe.session.user == 'Guest' %} Sign Up - {% elif not is_enrolled %} - + {% elif not has_access %} + {% endif %}

{% block script %} {% endblock %} From 24bd07d000d1e747a453bfd6ba5588fc6086922b Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 30 May 2019 16:36:58 +0530 Subject: [PATCH 14/40] refactor: style changes and template refactor --- erpnext/www/lms/content.html | 2 +- erpnext/www/lms/course.html | 24 ++++++++++++------------ erpnext/www/lms/program.html | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/erpnext/www/lms/content.html b/erpnext/www/lms/content.html index a908d85da3..68fb9f32fe 100644 --- a/erpnext/www/lms/content.html +++ b/erpnext/www/lms/content.html @@ -90,7 +90,7 @@ {% endif %}
-
+
{{ content.content }}
{% endmacro %} diff --git a/erpnext/www/lms/course.html b/erpnext/www/lms/course.html index 30b594d903..ee3b9758cb 100644 --- a/erpnext/www/lms/course.html +++ b/erpnext/www/lms/course.html @@ -35,16 +35,16 @@ {% macro card(topic, index, length) %}
- {% if frappe.session.user == 'Guest' %} - - {% else %} + {% if has_access %} + {% else %} +
{% endif %}
@@ -81,7 +81,7 @@ {% block content %}
- {{ hero(course.course_name, course.course_intro) }} + {{ hero(course.course_name, course.course_intro, has_access) }}
{% for topic in topics %} diff --git a/erpnext/www/lms/program.html b/erpnext/www/lms/program.html index 65c883fb4c..d364e5e1d9 100644 --- a/erpnext/www/lms/program.html +++ b/erpnext/www/lms/program.html @@ -40,7 +40,7 @@
{% else %}
-
{{ course.course_name }}
+
{% endif %}
@@ -54,7 +54,7 @@ {% block content %}
- {{ hero(program.program_name, program.description) }} + {{ hero(program.program_name, program.description, has_access) }}
{% for course in program.courses %} From 4991fca5ccc81f130b93bf6669e41ccd50313e4a Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 30 May 2019 16:37:15 +0530 Subject: [PATCH 15/40] feat: enabled per student access --- erpnext/www/lms/content.py | 36 +++++++++++++++++++++++++++++++----- erpnext/www/lms/course.py | 4 +++- erpnext/www/lms/program.py | 4 ++-- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/erpnext/www/lms/content.py b/erpnext/www/lms/content.py index 78b02e2778..67e3508df5 100644 --- a/erpnext/www/lms/content.py +++ b/erpnext/www/lms/content.py @@ -5,19 +5,27 @@ import frappe no_cache = 1 def get_context(context): - if frappe.session.user == "Guest": + program = frappe.form_dict['program'] + content = frappe.form_dict['content'] + content_type = frappe.form_dict['type'] + + has_program_access = utils.allowed_program_access(program) + has_content_access = allowed_content_access(program, content, content_type) + + if frappe.session.user == "Guest" or not has_program_access or not has_content_access: frappe.local.flags.redirect_location = '/lms' raise frappe.Redirect + context.content = frappe.get_doc(content_type, content).as_dict() + context.content_type = content_type + context.course = frappe.form_dict['course'] context.topic = frappe.form_dict['topic'] - content = frappe.form_dict['content'] - context.content_type = frappe.form_dict['type'] - context.content = frappe.get_doc(context.content_type, content).as_dict() context.previous = get_previous_content(context.topic, context.course, context.content, context.content_type) context.next = get_next_content(context.topic, context.course, context.content, context.content_type) + def get_next_content(topic, course, content, content_type): if frappe.session.user == "Guest": return None @@ -38,4 +46,22 @@ def get_previous_content(topic, course, content, content_type): if current_index == 0: return None else: - return content_list[current_index - 1] \ No newline at end of file + return content_list[current_index - 1] + +def allowed_content_access(program, content, content_type): + # Get all content in program + + # Using ORM + # course_in_program = [course.course for course in frappe.get_all('Program Course', fields=['course'], filters={'parent': program})] + # topics_in_course = [topic.topic for topic in frappe.get_all("Course Topic", fields=['topic'], filters=[['parent','in', course_in_program]])] + # contents_of_program = [[c.content, c.content_type] for c in frappe.get_all('Topic Content', fields=['content', 'content_type'], filters=[['parent','in', topics_in_course]])] + + contents_of_program = frappe.db.sql("""select `tabtopic content`.content, `tabtopic content`.content_type + from `tabcourse topic`, + `tabprogram course`, + `tabtopic content` + where `tabcourse topic`.parent = `tabprogram course`.course + and `tabtopic content`.parent = `tabcourse topic`.topic + and `tabprogram course`.parent = '{0}'""".format(program)) + + return (content, content_type) in contents_of_program \ No newline at end of file diff --git a/erpnext/www/lms/course.py b/erpnext/www/lms/course.py index d8670e6977..b9aff5c5bd 100644 --- a/erpnext/www/lms/course.py +++ b/erpnext/www/lms/course.py @@ -7,5 +7,7 @@ no_cache = 1 def get_context(context): context.education_settings = frappe.get_single("Education Settings") course = frappe.get_doc('Course', frappe.form_dict['name']) + context.program = frappe.form_dict['program'] context.course = course - context.topics = course.get_topics() \ No newline at end of file + context.topics = course.get_topics() + context.has_access = utils.allowed_program_access(context.program) \ No newline at end of file diff --git a/erpnext/www/lms/program.py b/erpnext/www/lms/program.py index 827b11a4ab..4c3a3fdf66 100644 --- a/erpnext/www/lms/program.py +++ b/erpnext/www/lms/program.py @@ -6,8 +6,8 @@ no_cache = 1 def get_context(context): context.education_settings = frappe.get_single("Education Settings") - context.program = get_program(frappe.form_dict['name']) - context.is_enrolled = utils.get_enrollment_status(program) + context.program = get_program(frappe.form_dict['program']) + context.has_access = utils.allowed_program_access(frappe.form_dict['program']) def get_program(program_name): try: From 12579617f3079e925ad0d1dca73ea580adbfbea1 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 30 May 2019 17:19:11 +0530 Subject: [PATCH 16/40] refactor: added enrolled status to program card --- erpnext/education/utils.py | 2 +- erpnext/www/lms/all-programs.html | 2 +- erpnext/www/lms/content.py | 2 +- erpnext/www/lms/index.html | 2 +- erpnext/www/lms/macros/card.html | 6 ++++-- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py index 09ac22ca27..c19bd19879 100644 --- a/erpnext/education/utils.py +++ b/erpnext/education/utils.py @@ -84,7 +84,7 @@ def get_portal_programs(): return None program_list = [frappe.get_doc("Program", program) for program in published_programs] - portal_programs = [program for program in program_list if allowed_program_access(program.name) or program.allow_self_enroll] + portal_programs = [{'program': program, 'has_access': allowed_program_access(program.name)} for program in program_list if allowed_program_access(program.name) or program.allow_self_enroll] return portal_programs diff --git a/erpnext/www/lms/all-programs.html b/erpnext/www/lms/all-programs.html index 088498b4a9..02df51acdf 100644 --- a/erpnext/www/lms/all-programs.html +++ b/erpnext/www/lms/all-programs.html @@ -42,7 +42,7 @@
{% for program in all_programs %} - {{ program_card(program) }} + {{ program_card(program.program, program.has_access) }} {% endfor %}
diff --git a/erpnext/www/lms/content.py b/erpnext/www/lms/content.py index 67e3508df5..25d5bc7e7f 100644 --- a/erpnext/www/lms/content.py +++ b/erpnext/www/lms/content.py @@ -16,7 +16,7 @@ def get_context(context): frappe.local.flags.redirect_location = '/lms' raise frappe.Redirect - context.content = frappe.get_doc(content_type, content).as_dict() + context.content = frappe.get_doc(content_type, content) context.content_type = content_type context.course = frappe.form_dict['course'] diff --git a/erpnext/www/lms/index.html b/erpnext/www/lms/index.html index ba3034c79e..6661e22ce3 100644 --- a/erpnext/www/lms/index.html +++ b/erpnext/www/lms/index.html @@ -46,7 +46,7 @@
{% for program in featured_programs %} - {{ program_card(program) }} + {{ program_card(program.program, program.has_access) }} {% endfor %}

diff --git a/erpnext/www/lms/macros/card.html b/erpnext/www/lms/macros/card.html index 8cf8a78e56..a0e4dab349 100644 --- a/erpnext/www/lms/macros/card.html +++ b/erpnext/www/lms/macros/card.html @@ -1,4 +1,4 @@ -{% macro program_card(program) %} +{% macro program_card(program, has_access) %}

From 3336fb595c05b62525a1dcb4b627f0514e74311a Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 30 May 2019 18:04:36 +0530 Subject: [PATCH 17/40] style: removed console.logs --- erpnext/www/lms/macros/hero.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/www/lms/macros/hero.html b/erpnext/www/lms/macros/hero.html index 89011b2891..20e9d05c60 100644 --- a/erpnext/www/lms/macros/hero.html +++ b/erpnext/www/lms/macros/hero.html @@ -20,7 +20,7 @@ function enroll() { let params = frappe.utils.get_query_params() - console.log(params.program) + let btn = document.getElementById('enroll'); btn.disbaled = true; btn.innerText = 'Enrolling...' @@ -33,7 +33,6 @@ } frappe.call(opts).then(res => { - console.log(res) let success_dialog = new frappe.ui.Dialog({ title: __('Success'), secondary_action: function() { From e94e9d2b06a15a5e1b017714dd3bdb6ec1a2c0a6 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 30 May 2019 18:05:00 +0530 Subject: [PATCH 18/40] feat: content navigation and activity tracking working --- .../course_enrollment/course_enrollment.py | 16 ++++-- erpnext/education/utils.py | 52 ++++++++---------- erpnext/www/lms/content.html | 23 +++++--- erpnext/www/lms/content.py | 55 ++++++++++--------- 4 files changed, 76 insertions(+), 70 deletions(-) diff --git a/erpnext/education/doctype/course_enrollment/course_enrollment.py b/erpnext/education/doctype/course_enrollment/course_enrollment.py index 6f2bb0db1f..064b075709 100644 --- a/erpnext/education/doctype/course_enrollment/course_enrollment.py +++ b/erpnext/education/doctype/course_enrollment/course_enrollment.py @@ -62,8 +62,9 @@ class CourseEnrollment(Document): }).insert() def add_activity(self, content_type, content): - if check_activity_exists(self.name, content_type, content): - pass + activity = check_activity_exists(self.name, content_type, content) + if activity: + return activity else: activity = frappe.get_doc({ "doctype": "Course Activity", @@ -71,9 +72,14 @@ class CourseEnrollment(Document): "content_type": content_type, "content": content, "activity_date": frappe.utils.datetime.datetime.now() - }) - activity.insert() + }) + + activity.insert(ignore_permissions=True) + return activity.name def check_activity_exists(enrollment, content_type, content): activity = frappe.get_all("Course Activity", filters={'enrollment': enrollment, 'content_type': content_type, 'content': content}) - return bool(activity) \ No newline at end of file + if activity: + return activity[0].name + else: + return None \ No newline at end of file diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py index c19bd19879..a4b71e310e 100644 --- a/erpnext/education/utils.py +++ b/erpnext/education/utils.py @@ -77,7 +77,7 @@ def get_portal_programs(): is_published and (student_is_enrolled or student_can_self_enroll) Returns: - list of objects: List of all programs and to be displayed on the portal along with enrollment status + list of dictionary: List of all programs and to be displayed on the portal along with access rights """ published_programs = frappe.get_all("Program", filters={"is_published": True}) if not published_programs: @@ -170,40 +170,34 @@ def enroll_in_program(program_name, student=None): return program_enrollment.name def has_super_access(): + """Check if user has a role that allows full access to LMS + + Returns: + bool: true if user has access to all lms content + """ current_user = frappe.get_doc('User', frappe.session.user) roles = set([role.role for role in current_user.roles]) return bool(roles & {'Administrator', 'Instructor', 'Education Manager', 'System Manager', 'Academic User'}) -# def get_program_enrollment(program_name): -# """ -# Function to get program enrollments for a particular student for a program -# """ -# student get_current_student() -# if not student: -# return None -# else: -# enrollment = frappe.get_all("Program Enrollment", filters={'student':student.name, 'program': program_name}) -# if enrollment: -# return enrollment[0].name -# else: -# return None +@frappe.whitelist() +def add_activity(course, content_type, content): + if has_super_access(): + return None -# def get_program_and_enrollment_status(program_name): -# program = frappe.get_doc('Program', program_name) -# is_enrolled = bool(get_program_enrollment(program_name)) or check_super_access() -# return {'program': program, 'is_enrolled': is_enrolled} + student = get_current_student() + if not student: + return frappe.throw("Student with email {0} does not exist".format(frappe.session.user), frappe.DoesNotExistError) -# def get_course_enrollment(course_name): -# student = get_current_student() -# if not student: -# return None -# enrollment_name = frappe.get_all("Course Enrollment", filters={'student': student.name, 'course':course_name}) -# try: -# name = enrollment_name[0].name -# enrollment = frappe.get_doc("Course Enrollment", name) -# return enrollment -# except: -# return None + course_enrollment = get_enrollment("course", course, student.name) + print(course_enrollment) + if not course_enrollment: + return None + + enrollment = frappe.get_doc('Course Enrollment', course_enrollment) + if content_type == 'Quiz': + return + else: + return enrollment.add_activity(content_type, content) def create_student_from_current_user(): user = frappe.get_doc("User", frappe.session.user) diff --git a/erpnext/www/lms/content.html b/erpnext/www/lms/content.html index 68fb9f32fe..a02b2c7500 100644 --- a/erpnext/www/lms/content.html +++ b/erpnext/www/lms/content.html @@ -34,19 +34,19 @@ {% macro navigation() %}
-

{{ content.name }}

+

{{ content.name }} ({{ position + 1 }}/{{length}})

{% if previous %} - Previous + Previous {% else %} - Back to Course + Back to Course {% endif %} {% if next %} - + {% else %} - + {% endif %}
@@ -126,13 +126,18 @@ }) function handle(url) { - frappe.call("add_activity", - { + + opts = { + method: "erpnext.education.utils.add_activity", + args: { course: "{{ course }}", content_type: "{{ content_type }}", - content: "{{ content.name }}", + content: "{{ content.name }}" } - ) + } + frappe.call(opts).then(res => { + window.location.href = url; + }) } {% endblock %} \ No newline at end of file diff --git a/erpnext/www/lms/content.py b/erpnext/www/lms/content.py index 25d5bc7e7f..51a8e32bad 100644 --- a/erpnext/www/lms/content.py +++ b/erpnext/www/lms/content.py @@ -5,10 +5,19 @@ import frappe no_cache = 1 def get_context(context): - program = frappe.form_dict['program'] - content = frappe.form_dict['content'] - content_type = frappe.form_dict['type'] + # Load Query Parameters + try: + program = frappe.form_dict['program'] + content = frappe.form_dict['content'] + content_type = frappe.form_dict['type'] + course = frappe.form_dict['course'] + topic = frappe.form_dict['topic'] + except KeyError: + frappe.local.flags.redirect_location = '/lms' + raise frappe.Redirect + + # Check if user has access to the content has_program_access = utils.allowed_program_access(program) has_content_access = allowed_content_access(program, content, content_type) @@ -16,46 +25,38 @@ def get_context(context): frappe.local.flags.redirect_location = '/lms' raise frappe.Redirect + + # Set context for content to be displayer context.content = frappe.get_doc(content_type, content) context.content_type = content_type + context.program = program + context.course = course + context.topic = topic - context.course = frappe.form_dict['course'] - context.topic = frappe.form_dict['topic'] - - context.previous = get_previous_content(context.topic, context.course, context.content, context.content_type) - context.next = get_next_content(context.topic, context.course, context.content, context.content_type) - - -def get_next_content(topic, course, content, content_type): - if frappe.session.user == "Guest": - return None topic = frappe.get_doc("Topic", topic) content_list = [{'content_type':item.doctype, 'content':item.name} for item in topic.get_contents()] - current_index = content_list.index({'content': content.name, 'content_type': content_type}) + + # Set context for progress numbers + context.position = content_list.index({'content': content, 'content_type': content_type}) + context.length = len(content_list) + + # Set context for navigation + context.previous = get_previous_content(content_list, context.position) + context.next = get_next_content(content_list, context.position) + +def get_next_content(content_list, current_index): try: return content_list[current_index + 1] except IndexError: return None -def get_previous_content(topic, course, content, content_type): - if frappe.session.user == "Guest": - return None - topic = frappe.get_doc("Topic", topic) - content_list = [{'content_type':item.doctype, 'content':item.name} for item in topic.get_contents()] - current_index = content_list.index({'content': content.name, 'content_type': content_type}) +def get_previous_content(content_list, current_index): if current_index == 0: return None else: return content_list[current_index - 1] def allowed_content_access(program, content, content_type): - # Get all content in program - - # Using ORM - # course_in_program = [course.course for course in frappe.get_all('Program Course', fields=['course'], filters={'parent': program})] - # topics_in_course = [topic.topic for topic in frappe.get_all("Course Topic", fields=['topic'], filters=[['parent','in', course_in_program]])] - # contents_of_program = [[c.content, c.content_type] for c in frappe.get_all('Topic Content', fields=['content', 'content_type'], filters=[['parent','in', topics_in_course]])] - contents_of_program = frappe.db.sql("""select `tabtopic content`.content, `tabtopic content`.content_type from `tabcourse topic`, `tabprogram course`, From 253a2bd26042eea776a39cbcd72d9fd67030f2d0 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 30 May 2019 18:29:46 +0530 Subject: [PATCH 19/40] =?UTF-8?q?refactor:=20goodbye=20Vue=20=F0=9F=91=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- erpnext/public/build.json | 3 - erpnext/public/js/education/lms/call.js | 15 --- .../js/education/lms/components/Article.vue | 44 ------- .../education/lms/components/Breadcrumb.vue | 56 --------- .../js/education/lms/components/Button.vue | 25 ---- .../js/education/lms/components/CardList.vue | 28 ----- .../lms/components/ContentNavigation.vue | 40 ------ .../education/lms/components/ContentTitle.vue | 29 ----- .../education/lms/components/CourseCard.vue | 87 ------------- .../js/education/lms/components/Navbar.vue | 85 ------------- .../education/lms/components/ProfileInfo.vue | 83 ------------ .../education/lms/components/ProgramCard.vue | 82 ------------ .../education/lms/components/ProgressCard.vue | 89 ------------- .../js/education/lms/components/Quiz.vue | 119 ------------------ .../components/Quiz/QuizMultipleChoice.vue | 34 ----- .../lms/components/Quiz/QuizSingleChoice.vue | 28 ----- .../js/education/lms/components/ScoreCard.vue | 60 --------- .../education/lms/components/TopSection.vue | 27 ---- .../lms/components/TopSectionButton.vue | 49 -------- .../js/education/lms/components/TopicCard.vue | 112 ----------------- .../js/education/lms/components/Video.vue | 63 ---------- .../education/lms/components/VideoModal.vue | 35 ------ .../lms/components/YoutubePlayer.vue | 36 ------ erpnext/public/js/education/lms/lms.js | 81 ------------ erpnext/public/js/education/lms/lmsRoot.vue | 45 ------- .../js/education/lms/pages/ContentPage.vue | 84 ------------- .../js/education/lms/pages/CoursePage.vue | 49 -------- .../public/js/education/lms/pages/Home.vue | 48 ------- .../js/education/lms/pages/ListPage.vue | 53 -------- .../js/education/lms/pages/ProfilePage.vue | 50 -------- .../js/education/lms/pages/ProgramPage.vue | 49 -------- erpnext/public/js/education/lms/routes.js | 92 -------------- 32 files changed, 1780 deletions(-) delete mode 100644 erpnext/public/js/education/lms/call.js delete mode 100644 erpnext/public/js/education/lms/components/Article.vue delete mode 100644 erpnext/public/js/education/lms/components/Breadcrumb.vue delete mode 100644 erpnext/public/js/education/lms/components/Button.vue delete mode 100644 erpnext/public/js/education/lms/components/CardList.vue delete mode 100644 erpnext/public/js/education/lms/components/ContentNavigation.vue delete mode 100644 erpnext/public/js/education/lms/components/ContentTitle.vue delete mode 100644 erpnext/public/js/education/lms/components/CourseCard.vue delete mode 100644 erpnext/public/js/education/lms/components/Navbar.vue delete mode 100644 erpnext/public/js/education/lms/components/ProfileInfo.vue delete mode 100644 erpnext/public/js/education/lms/components/ProgramCard.vue delete mode 100644 erpnext/public/js/education/lms/components/ProgressCard.vue delete mode 100644 erpnext/public/js/education/lms/components/Quiz.vue delete mode 100644 erpnext/public/js/education/lms/components/Quiz/QuizMultipleChoice.vue delete mode 100644 erpnext/public/js/education/lms/components/Quiz/QuizSingleChoice.vue delete mode 100644 erpnext/public/js/education/lms/components/ScoreCard.vue delete mode 100644 erpnext/public/js/education/lms/components/TopSection.vue delete mode 100644 erpnext/public/js/education/lms/components/TopSectionButton.vue delete mode 100644 erpnext/public/js/education/lms/components/TopicCard.vue delete mode 100644 erpnext/public/js/education/lms/components/Video.vue delete mode 100644 erpnext/public/js/education/lms/components/VideoModal.vue delete mode 100644 erpnext/public/js/education/lms/components/YoutubePlayer.vue delete mode 100644 erpnext/public/js/education/lms/lms.js delete mode 100644 erpnext/public/js/education/lms/lmsRoot.vue delete mode 100644 erpnext/public/js/education/lms/pages/ContentPage.vue delete mode 100644 erpnext/public/js/education/lms/pages/CoursePage.vue delete mode 100644 erpnext/public/js/education/lms/pages/Home.vue delete mode 100644 erpnext/public/js/education/lms/pages/ListPage.vue delete mode 100644 erpnext/public/js/education/lms/pages/ProfilePage.vue delete mode 100644 erpnext/public/js/education/lms/pages/ProgramPage.vue delete mode 100644 erpnext/public/js/education/lms/routes.js diff --git a/erpnext/public/build.json b/erpnext/public/build.json index 45de6eb294..60e72dad71 100644 --- a/erpnext/public/build.json +++ b/erpnext/public/build.json @@ -54,8 +54,5 @@ "stock/dashboard/item_dashboard.html", "stock/dashboard/item_dashboard_list.html", "stock/dashboard/item_dashboard.js" - ], - "js/lms.min.js": [ - "public/js/education/lms/lms.js" ] } diff --git a/erpnext/public/js/education/lms/call.js b/erpnext/public/js/education/lms/call.js deleted file mode 100644 index 4edcaaa6d6..0000000000 --- a/erpnext/public/js/education/lms/call.js +++ /dev/null @@ -1,15 +0,0 @@ -frappe.ready(() => { - frappe.provide('lms'); - - lms.call = (method, args) => { - const method_path = 'erpnext.www.lms_legacy.' + method; - return new Promise((resolve, reject) => { - return frappe.call({ - method: method_path, - args, - }) - .then(r => resolve(r.message)) - .fail(reject); - }); - }; -}); \ No newline at end of file diff --git a/erpnext/public/js/education/lms/components/Article.vue b/erpnext/public/js/education/lms/components/Article.vue deleted file mode 100644 index eab1424455..0000000000 --- a/erpnext/public/js/education/lms/components/Article.vue +++ /dev/null @@ -1,44 +0,0 @@ - - diff --git a/erpnext/public/js/education/lms/components/Breadcrumb.vue b/erpnext/public/js/education/lms/components/Breadcrumb.vue deleted file mode 100644 index 1b617a3751..0000000000 --- a/erpnext/public/js/education/lms/components/Breadcrumb.vue +++ /dev/null @@ -1,56 +0,0 @@ - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/components/Button.vue b/erpnext/public/js/education/lms/components/Button.vue deleted file mode 100644 index 4d8df4b314..0000000000 --- a/erpnext/public/js/education/lms/components/Button.vue +++ /dev/null @@ -1,25 +0,0 @@ - - diff --git a/erpnext/public/js/education/lms/components/CardList.vue b/erpnext/public/js/education/lms/components/CardList.vue deleted file mode 100644 index 10f6af096c..0000000000 --- a/erpnext/public/js/education/lms/components/CardList.vue +++ /dev/null @@ -1,28 +0,0 @@ - - - diff --git a/erpnext/public/js/education/lms/components/ContentNavigation.vue b/erpnext/public/js/education/lms/components/ContentNavigation.vue deleted file mode 100644 index a07c0f85f4..0000000000 --- a/erpnext/public/js/education/lms/components/ContentNavigation.vue +++ /dev/null @@ -1,40 +0,0 @@ - - - - - diff --git a/erpnext/public/js/education/lms/components/ContentTitle.vue b/erpnext/public/js/education/lms/components/ContentTitle.vue deleted file mode 100644 index a488ab85c3..0000000000 --- a/erpnext/public/js/education/lms/components/ContentTitle.vue +++ /dev/null @@ -1,29 +0,0 @@ - - - - - diff --git a/erpnext/public/js/education/lms/components/CourseCard.vue b/erpnext/public/js/education/lms/components/CourseCard.vue deleted file mode 100644 index 48a9f591c7..0000000000 --- a/erpnext/public/js/education/lms/components/CourseCard.vue +++ /dev/null @@ -1,87 +0,0 @@ - - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/components/Navbar.vue b/erpnext/public/js/education/lms/components/Navbar.vue deleted file mode 100644 index f3f3ce4cbb..0000000000 --- a/erpnext/public/js/education/lms/components/Navbar.vue +++ /dev/null @@ -1,85 +0,0 @@ - - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/components/ProfileInfo.vue b/erpnext/public/js/education/lms/components/ProfileInfo.vue deleted file mode 100644 index 5bad713997..0000000000 --- a/erpnext/public/js/education/lms/components/ProfileInfo.vue +++ /dev/null @@ -1,83 +0,0 @@ - - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/components/ProgramCard.vue b/erpnext/public/js/education/lms/components/ProgramCard.vue deleted file mode 100644 index 15a9fcdcd2..0000000000 --- a/erpnext/public/js/education/lms/components/ProgramCard.vue +++ /dev/null @@ -1,82 +0,0 @@ - - - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/components/ProgressCard.vue b/erpnext/public/js/education/lms/components/ProgressCard.vue deleted file mode 100644 index 66b61f694e..0000000000 --- a/erpnext/public/js/education/lms/components/ProgressCard.vue +++ /dev/null @@ -1,89 +0,0 @@ - - - diff --git a/erpnext/public/js/education/lms/components/Quiz.vue b/erpnext/public/js/education/lms/components/Quiz.vue deleted file mode 100644 index 0a6199a756..0000000000 --- a/erpnext/public/js/education/lms/components/Quiz.vue +++ /dev/null @@ -1,119 +0,0 @@ - - - - - diff --git a/erpnext/public/js/education/lms/components/Quiz/QuizMultipleChoice.vue b/erpnext/public/js/education/lms/components/Quiz/QuizMultipleChoice.vue deleted file mode 100644 index 338b1ac0c5..0000000000 --- a/erpnext/public/js/education/lms/components/Quiz/QuizMultipleChoice.vue +++ /dev/null @@ -1,34 +0,0 @@ - - - - - diff --git a/erpnext/public/js/education/lms/components/Quiz/QuizSingleChoice.vue b/erpnext/public/js/education/lms/components/Quiz/QuizSingleChoice.vue deleted file mode 100644 index 235cbce4ae..0000000000 --- a/erpnext/public/js/education/lms/components/Quiz/QuizSingleChoice.vue +++ /dev/null @@ -1,28 +0,0 @@ - - - - - diff --git a/erpnext/public/js/education/lms/components/ScoreCard.vue b/erpnext/public/js/education/lms/components/ScoreCard.vue deleted file mode 100644 index 80b12cb6f6..0000000000 --- a/erpnext/public/js/education/lms/components/ScoreCard.vue +++ /dev/null @@ -1,60 +0,0 @@ - - - diff --git a/erpnext/public/js/education/lms/components/TopSection.vue b/erpnext/public/js/education/lms/components/TopSection.vue deleted file mode 100644 index c27d0031ef..0000000000 --- a/erpnext/public/js/education/lms/components/TopSection.vue +++ /dev/null @@ -1,27 +0,0 @@ - - - diff --git a/erpnext/public/js/education/lms/components/TopSectionButton.vue b/erpnext/public/js/education/lms/components/TopSectionButton.vue deleted file mode 100644 index 0fa49d4da5..0000000000 --- a/erpnext/public/js/education/lms/components/TopSectionButton.vue +++ /dev/null @@ -1,49 +0,0 @@ - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/components/TopicCard.vue b/erpnext/public/js/education/lms/components/TopicCard.vue deleted file mode 100644 index 4cb8e85c3b..0000000000 --- a/erpnext/public/js/education/lms/components/TopicCard.vue +++ /dev/null @@ -1,112 +0,0 @@ - - - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/components/Video.vue b/erpnext/public/js/education/lms/components/Video.vue deleted file mode 100644 index 50b4dd460d..0000000000 --- a/erpnext/public/js/education/lms/components/Video.vue +++ /dev/null @@ -1,63 +0,0 @@ - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/components/VideoModal.vue b/erpnext/public/js/education/lms/components/VideoModal.vue deleted file mode 100644 index 71227ade2c..0000000000 --- a/erpnext/public/js/education/lms/components/VideoModal.vue +++ /dev/null @@ -1,35 +0,0 @@ - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/components/YoutubePlayer.vue b/erpnext/public/js/education/lms/components/YoutubePlayer.vue deleted file mode 100644 index 9377b57d3b..0000000000 --- a/erpnext/public/js/education/lms/components/YoutubePlayer.vue +++ /dev/null @@ -1,36 +0,0 @@ - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/lms.js b/erpnext/public/js/education/lms/lms.js deleted file mode 100644 index 4665b144c2..0000000000 --- a/erpnext/public/js/education/lms/lms.js +++ /dev/null @@ -1,81 +0,0 @@ -import Vue from 'vue/dist/vue.js'; -import VueRouter from 'vue-router/dist/vue-router.js'; -import moment from 'moment/min/moment.min.js'; - -import lmsRoot from "./lmsRoot.vue"; -import routes from './routes'; -import './call'; - -Vue.use(VueRouter); - -var store = { - enrolledPrograms: [], - enrolledCourses: [] -}; - -// let profile_page = ` LMS Profile ` -// document.querySelector('#website-post-login > ul').innerHTML += profile_page - -frappe.ready(() => { - frappe.provide('lms'); - - lms.moment = moment; - - lms.store = new Vue({ - data: store, - methods: { - updateEnrolledPrograms() { - if(this.checkLogin()) { - lms.call("get_program_enrollments").then(data => { - this.enrolledPrograms = data; - }); - } - }, - updateEnrolledCourses() { - if(this.checkLogin()) { - lms.call("get_all_course_enrollments").then(data => { - this.enrolledCourses = data; - }); - } - }, - checkLogin() { - return frappe.is_user_logged_in(); - }, - updateState() { - this.checkLogin(); - this.updateEnrolledPrograms(); - this.updateEnrolledCourses(); - }, - checkProgramEnrollment(programName) { - if(this.checkLogin()){ - if(this.enrolledPrograms) { - if(this.enrolledPrograms.includes(programName)) { - return true; - } - else { - return false; - } - } - else { - return false; - } - } - else { - return false; - } - } - } - }); - lms.view = new Vue({ - el: "#lms-app", - router: new VueRouter({ routes }), - template: "", - components: { lmsRoot }, - mounted() { - lms.store.updateState(); - } - }); - lms.view.$router.afterEach((to, from) => { - window.scrollTo(0,0); - }); -}); \ No newline at end of file diff --git a/erpnext/public/js/education/lms/lmsRoot.vue b/erpnext/public/js/education/lms/lmsRoot.vue deleted file mode 100644 index d359265c58..0000000000 --- a/erpnext/public/js/education/lms/lmsRoot.vue +++ /dev/null @@ -1,45 +0,0 @@ - - - diff --git a/erpnext/public/js/education/lms/pages/ContentPage.vue b/erpnext/public/js/education/lms/pages/ContentPage.vue deleted file mode 100644 index 224ee03a4a..0000000000 --- a/erpnext/public/js/education/lms/pages/ContentPage.vue +++ /dev/null @@ -1,84 +0,0 @@ - - - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/pages/CoursePage.vue b/erpnext/public/js/education/lms/pages/CoursePage.vue deleted file mode 100644 index dc3d13052b..0000000000 --- a/erpnext/public/js/education/lms/pages/CoursePage.vue +++ /dev/null @@ -1,49 +0,0 @@ - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/pages/Home.vue b/erpnext/public/js/education/lms/pages/Home.vue deleted file mode 100644 index 6554a76587..0000000000 --- a/erpnext/public/js/education/lms/pages/Home.vue +++ /dev/null @@ -1,48 +0,0 @@ - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/pages/ListPage.vue b/erpnext/public/js/education/lms/pages/ListPage.vue deleted file mode 100644 index cf5cecce9c..0000000000 --- a/erpnext/public/js/education/lms/pages/ListPage.vue +++ /dev/null @@ -1,53 +0,0 @@ - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/pages/ProfilePage.vue b/erpnext/public/js/education/lms/pages/ProfilePage.vue deleted file mode 100644 index beff5eb34e..0000000000 --- a/erpnext/public/js/education/lms/pages/ProfilePage.vue +++ /dev/null @@ -1,50 +0,0 @@ - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/pages/ProgramPage.vue b/erpnext/public/js/education/lms/pages/ProgramPage.vue deleted file mode 100644 index 415c861e81..0000000000 --- a/erpnext/public/js/education/lms/pages/ProgramPage.vue +++ /dev/null @@ -1,49 +0,0 @@ - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/routes.js b/erpnext/public/js/education/lms/routes.js deleted file mode 100644 index 483f2220c3..0000000000 --- a/erpnext/public/js/education/lms/routes.js +++ /dev/null @@ -1,92 +0,0 @@ -import Home from "./pages/Home.vue"; -import ProgramPage from "./pages/ProgramPage.vue"; -import CoursePage from "./pages/CoursePage.vue"; -import ContentPage from "./pages/ContentPage.vue"; -import ListPage from "./pages/ListPage.vue"; -import ProfilePage from "./pages/ProfilePage.vue"; - -const routes = [{ - name: 'home', - path: '', - component: Home -}, -{ - name: 'program', - path: '/Program/:program_name', - component: ProgramPage, - props: true -}, -{ - name: 'course', - path: '/Program/:program_name/:course_name/', - component: CoursePage, - props: true, -}, -{ - name: 'content', - path: '/Program/:program_name/:course_name/:topic/:type/:content', - component: ContentPage, - props: true, - beforeRouteUpdate (to, from, next) { - if (lms.store.checkProgramEnrollment(to.params.program_name)) { - next(); - } else { - next({ - name: 'program', - params: { - program_name: to.params.program_name - } - }); - } - } -}, -{ - name: 'list', - path: '/List/:master', - component: ListPage, - props: true -}, -{ - name: 'signup', - path: '/Signup', - beforeEnter(to, from, next) { - window.location = window.location.origin.toString() + '/login#signup'; - }, - component: Home, - props: true -}, -{ - name: 'login', - path: '/Login', - beforeEnter(to, from, next) { - window.location = window.location.origin.toString() + '/login#login'; - }, - component: Home, - props: true -}, -{ - name: 'logout', - path: '/Logout', - beforeEnter(to, from, next) { - window.location = window.location.origin.toString() + '/?cmd=web_logout'; - }, - component: Home, - props: true -}, -{ - name: 'profile', - path: '/Profile', - component: ProfilePage, - props: true, - beforeEnter: (to, from, next) => { - if (!lms.store.checkLogin()) { - next({ - name: 'home' - }); - } else { - next(); - } - } -}]; - -export default routes; \ No newline at end of file From 7c7053fcf59f7f22b0fb3e16ace6d7719dda08df Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 30 May 2019 18:54:58 +0530 Subject: [PATCH 20/40] refactor: added question type field --- .../education/doctype/question/question.json | 221 ++++++------------ .../education/doctype/question/question.py | 8 + 2 files changed, 75 insertions(+), 154 deletions(-) diff --git a/erpnext/education/doctype/question/question.json b/erpnext/education/doctype/question/question.json index 14a9f3ce92..b3a161daa0 100644 --- a/erpnext/education/doctype/question/question.json +++ b/erpnext/education/doctype/question/question.json @@ -1,167 +1,80 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 0, - "autoname": "format:QUESTION-{#####}", - "beta": 0, - "creation": "2018-10-01 15:58:00.696815", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "allow_import": 1, + "autoname": "format:QUESTION-{#####}", + "creation": "2018-10-01 15:58:00.696815", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "question", + "options", + "question_type" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "question", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Question", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "question", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Question", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fetch_if_empty": 0, - "fieldname": "options", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Options", - "length": 0, - "no_copy": 0, - "options": "Options", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "options", + "fieldtype": "Table", + "label": "Options", + "options": "Options", + "reqd": 1 + }, + { + "fieldname": "question_type", + "fieldtype": "Select", + "in_standard_filter": 1, + "label": "Type", + "options": "\nSingle Correct Answer\nMultiple Correct Answer", + "read_only": 1 } - ], - "has_web_view": 0, - "hide_toolbar": 0, - "idx": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-04-22 14:02:08.140652", - "modified_by": "Administrator", - "module": "Education", - "name": "Question", - "name_case": "", - "owner": "Administrator", + ], + "modified": "2019-05-30 18:39:21.880974", + "modified_by": "Administrator", + "module": "Education", + "name": "Question", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Academics User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Academics User", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Instructor", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Instructor", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "LMS User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "LMS User", + "share": 1 } - ], - "quick_entry": 1, - "read_only": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/education/doctype/question/question.py b/erpnext/education/doctype/question/question.py index 8cd23983ce..b8221081fa 100644 --- a/erpnext/education/doctype/question/question.py +++ b/erpnext/education/doctype/question/question.py @@ -12,6 +12,7 @@ class Question(Document): def validate(self): self.check_at_least_one_option() self.check_minimum_one_correct_answer() + self.set_question_type() def check_at_least_one_option(self): if len(self.options) <= 1: @@ -26,6 +27,13 @@ class Question(Document): else: frappe.throw(_("A qustion must have at least one correct options")) + def set_question_type(self): + correct_options = [option for option in self.options if option.is_correct] + if len(correct_options) > 1: + self.question_type = "Multiple Correct Answer" + else: + self.question_type = "Single Correct Answer" + def get_answer(self): options = self.options answers = [item.name for item in options if item.is_correct == True] From 46b3446da050d1822b2b8fd6e607f969195601fd Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 30 May 2019 18:55:16 +0530 Subject: [PATCH 21/40] refactor: add check for max passing score --- erpnext/education/doctype/quiz/quiz.json | 232 ++--------------------- erpnext/education/doctype/quiz/quiz.py | 3 + 2 files changed, 22 insertions(+), 213 deletions(-) diff --git a/erpnext/education/doctype/quiz/quiz.json b/erpnext/education/doctype/quiz/quiz.json index f91bc0f021..b4903fc285 100644 --- a/erpnext/education/doctype/quiz/quiz.json +++ b/erpnext/education/doctype/quiz/quiz.json @@ -1,299 +1,105 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, "autoname": "field:title", - "beta": 0, "creation": "2018-10-17 05:52:50.149904", - "custom": 0, - "docstatus": 0, "doctype": "DocType", - "document_type": "", "editable_grid": 1, "engine": "InnoDB", + "field_order": [ + "title", + "question", + "quiz_configuration_section", + "passing_score", + "max_attempts", + "grading_basis" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "title", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Title", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, "unique": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "question", "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Question", - "length": 0, - "no_copy": 0, "options": "Quiz Question", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "quiz_configuration_section", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Quiz Configuration", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Quiz Configuration" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "75", + "description": "Score out of 100", "fieldname": "passing_score", "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Passing Score", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "1", "description": "Enter 0 to waive limit", "fieldname": "max_attempts", "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Max Attempts", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Last Highest Score", + "default": "Latest Highest Score", "fieldname": "grading_basis", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Grading Basis", - "length": 0, - "no_copy": 0, - "options": "\nLast Attempt\nLast Highest Score", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Latest Highest Score\nLatest Attempt" } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-11-25 19:07:36.190116", + "modified": "2019-05-30 18:50:54.218571", "modified_by": "Administrator", "module": "Education", "name": "Quiz", - "name_case": "", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Academics User", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "LMS User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 + "share": 1 }, { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Instructor", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 } ], "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/education/doctype/quiz/quiz.py b/erpnext/education/doctype/quiz/quiz.py index 6da50a6e25..6d00d33372 100644 --- a/erpnext/education/doctype/quiz/quiz.py +++ b/erpnext/education/doctype/quiz/quiz.py @@ -7,6 +7,9 @@ import frappe from frappe.model.document import Document class Quiz(Document): + def validate(self): + if self.passing_score > 100: + frappe.throw("Passing Score value should be between 0 and 100") def validate_quiz_attempts(self, enrollment, quiz_name): if self.max_attempts > 0: From d1a252190b657f4ea64819760b5f653b7a00428e Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 3 Jun 2019 12:57:38 +0530 Subject: [PATCH 22/40] refactor: refactored quiz api and added quiz.js --- .../course_enrollment/course_enrollment.py | 8 +- erpnext/education/doctype/quiz/quiz.py | 49 ++--- .../doctype/quiz_result/quiz_result.json | 183 +++++------------ erpnext/education/utils.py | 56 +++++- erpnext/public/js/education/lms/quiz.js | 185 ++++++++++++++++++ erpnext/www/lms/content.html | 47 ++++- erpnext/www/lms/content.py | 2 +- 7 files changed, 350 insertions(+), 180 deletions(-) create mode 100644 erpnext/public/js/education/lms/quiz.js diff --git a/erpnext/education/doctype/course_enrollment/course_enrollment.py b/erpnext/education/doctype/course_enrollment/course_enrollment.py index 064b075709..b082be2aa2 100644 --- a/erpnext/education/doctype/course_enrollment/course_enrollment.py +++ b/erpnext/education/doctype/course_enrollment/course_enrollment.py @@ -35,7 +35,7 @@ class CourseEnrollment(Document): if enrollment: frappe.throw(_("Student is already enrolled.")) - def add_quiz_activity(self, quiz_name, quiz_response,answers, score, status): + def add_quiz_activity(self, quiz_name, quiz_response, answers, score, status): result = {k: ('Correct' if v else 'Wrong') for k,v in answers.items()} result_data = [] for key in answers: @@ -43,7 +43,9 @@ class CourseEnrollment(Document): item['question'] = key item['quiz_result'] = result[key] try: - if isinstance(quiz_response[key], list): + if not quiz_response[key]: + item['selected_option'] = "Unattempted" + elif isinstance(quiz_response[key], list): item['selected_option'] = ', '.join(frappe.get_value('Options', res, 'option') for res in quiz_response[key]) else: item['selected_option'] = frappe.get_value('Options', quiz_response[key], 'option') @@ -59,7 +61,7 @@ class CourseEnrollment(Document): "result": result_data, "score": score, "status": status - }).insert() + }).insert(ignore_permissions = True) def add_activity(self, content_type, content): activity = check_activity_exists(self.name, content_type, content) diff --git a/erpnext/education/doctype/quiz/quiz.py b/erpnext/education/doctype/quiz/quiz.py index 6d00d33372..8e54745464 100644 --- a/erpnext/education/doctype/quiz/quiz.py +++ b/erpnext/education/doctype/quiz/quiz.py @@ -11,50 +11,43 @@ class Quiz(Document): if self.passing_score > 100: frappe.throw("Passing Score value should be between 0 and 100") - def validate_quiz_attempts(self, enrollment, quiz_name): - if self.max_attempts > 0: - try: - if len(frappe.get_all("Quiz Activity", {'enrollment': enrollment.name, 'quiz': quiz_name})) >= self.max_attempts: - frappe.throw('Maximum attempts reached!') - except Exception as e: - pass + def allowed_attempt(self, enrollment, quiz_name): + if self.max_attempts == 0: + return True + + try: + if len(frappe.get_all("Quiz Activity", {'enrollment': enrollment.name, 'quiz': quiz_name})) >= self.max_attempts: + frappe.msgprint("Maximum attempts for this quiz reached!") + return False + else: + return True + except Exception as e: + return False def evaluate(self, response_dict, quiz_name): - # self.validate_quiz_attempts(enrollment, quiz_name) questions = [frappe.get_doc('Question', question.question_link) for question in self.question] answers = {q.name:q.get_answer() for q in questions} - correct_answers = {} + result = {} for key in answers: try: if isinstance(response_dict[key], list): - result = compare_list_elementwise(response_dict[key], answers[key]) + is_correct = compare_list_elementwise(response_dict[key], answers[key]) else: - result = (response_dict[key] == answers[key]) - except: - result = False - correct_answers[key] = result - score = (sum(correct_answers.values()) * 100 ) / len(answers) + is_correct = (response_dict[key] == answers[key]) + except Exception as e: + is_correct = False + result[key] = is_correct + score = (sum(result.values()) * 100 ) / len(answers) if score >= self.passing_score: status = "Pass" else: status = "Fail" - return correct_answers, score, status + return result, score, status def get_questions(self): - quiz_question = self.get_all_children() - if quiz_question: - questions = [frappe.get_doc('Question', question.question_link).as_dict() for question in quiz_question] - for question in questions: - correct_options = [option.is_correct for option in question.options] - if sum(correct_options) > 1: - question['type'] = "MultipleChoice" - else: - question['type'] = "SingleChoice" - return questions - else: - return None + return [frappe.get_doc('Question', question.question_link) for question in self.question] def compare_list_elementwise(*args): try: diff --git a/erpnext/education/doctype/quiz_result/quiz_result.json b/erpnext/education/doctype/quiz_result/quiz_result.json index 86505ac756..67c7e2d449 100644 --- a/erpnext/education/doctype/quiz_result/quiz_result.json +++ b/erpnext/education/doctype/quiz_result/quiz_result.json @@ -1,145 +1,52 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2018-10-15 15:52:25.766374", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "creation": "2018-10-15 15:52:25.766374", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "question", + "selected_option", + "quiz_result" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "question", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Question", - "length": 0, - "no_copy": 0, - "options": "Question", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, - "unique": 0 - }, + "fieldname": "question", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Question", + "options": "Question", + "read_only": 1, + "reqd": 1, + "set_only_once": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "selected_option", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Selected Option", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, - "unique": 0 - }, + "fieldname": "selected_option", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Selected Option", + "read_only": 1, + "set_only_once": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "quiz_result", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Result", - "length": 0, - "no_copy": 0, - "options": "\nCorrect\nWrong", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, - "unique": 0 + "fieldname": "quiz_result", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Result", + "options": "\nCorrect\nWrong", + "read_only": 1, + "reqd": 1, + "set_only_once": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2019-03-27 17:58:54.388848", - "modified_by": "Administrator", - "module": "Education", - "name": "Quiz Result", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "istable": 1, + "modified": "2019-06-03 12:52:32.267392", + "modified_by": "Administrator", + "module": "Education", + "name": "Quiz Result", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py index a4b71e310e..53f02f5f2f 100644 --- a/erpnext/education/utils.py +++ b/erpnext/education/utils.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For lice from __future__ import unicode_literals, division import frappe @@ -173,7 +172,7 @@ def has_super_access(): """Check if user has a role that allows full access to LMS Returns: - bool: true if user has access to all lms content + bool: true if user has access to all lms content """ current_user = frappe.get_doc('User', frappe.session.user) roles = set([role.role for role in current_user.roles]) @@ -189,7 +188,6 @@ def add_activity(course, content_type, content): return frappe.throw("Student with email {0} does not exist".format(frappe.session.user), frappe.DoesNotExistError) course_enrollment = get_enrollment("course", course, student.name) - print(course_enrollment) if not course_enrollment: return None @@ -199,6 +197,56 @@ def add_activity(course, content_type, content): else: return enrollment.add_activity(content_type, content) +@frappe.whitelist() +def evaluate_quiz(quiz_response, quiz_name, course): + import json + + student = get_current_student() + + quiz_response = json.loads(quiz_response) + quiz = frappe.get_doc("Quiz", quiz_name) + result, score, status = quiz.evaluate(quiz_response, quiz_name) + + if has_super_access(): + return {'result': result, 'score': score, 'status': status} + + if student: + course_enrollment = get_enrollment("course", course, student.name) + if course_enrollment: + enrollment = frappe.get_doc('Course Enrollment', course_enrollment) + if quiz.allowed_attempt(enrollment, quiz_name): + enrollment.add_quiz_activity(quiz_name, quiz_response, result, score, status) + return {'result': result, 'score': score, 'status': status} + else: + return None + else: + frappe.throw("Something went wrong. Pleae contact the administrator.") + +@frappe.whitelist() +def get_quiz(quiz_name, course): + try: + quiz = frappe.get_doc("Quiz", quiz_name) + questions = quiz.get_questions() + except: + frappe.throw("Quiz {0} does not exist".format(quiz_name)) + return None + + questions = [{ + 'name': question.name, + 'question': question.question, + 'type': question.question_type, + 'options': [{'name': option.name, 'option': option.option} + for option in question.options], + } for question in questions] + + if has_super_access(): + return {'questions': questions, 'activity': None} + + student = get_current_student() + course_enrollment = get_enrollment("course", course, student.name) + status, score, result = check_quiz_completion(quiz, course_enrollment) + return {'questions': questions, 'activity': {'is_complete': status, 'score': score, 'result': result}} + def create_student_from_current_user(): user = frappe.get_doc("User", frappe.session.user) @@ -226,7 +274,7 @@ def check_content_completion(content_name, content_type, enrollment_name): def check_quiz_completion(quiz, enrollment_name): attempts = frappe.get_all("Quiz Activity", filters={'enrollment': enrollment_name, 'quiz': quiz.name}, fields=["name", "activity_date", "score", "status"]) - status = False if quiz.max_attempts == 0 else bool(len(attempts) == quiz.max_attempts) + status = False if quiz.max_attempts == 0 else bool(len(attempts) >= quiz.max_attempts) score = None result = None if attempts: diff --git a/erpnext/public/js/education/lms/quiz.js b/erpnext/public/js/education/lms/quiz.js new file mode 100644 index 0000000000..f6dc4d08d5 --- /dev/null +++ b/erpnext/public/js/education/lms/quiz.js @@ -0,0 +1,185 @@ +class Quiz { + constructor(wrapper, options) { + this.wrapper = wrapper; + Object.assign(this, options); + this.questions = [] + this.refresh(); + } + + refresh() { + this.get_quiz(); + } + + get_quiz() { + frappe.call('erpnext.education.utils.get_quiz', { + quiz_name: this.name, + course: this.course + }).then(res => { + this.make(res.message) + }); + } + + make(data) { + data.questions.forEach(question_data => { + let question_wrapper = document.createElement('div'); + let question = new Question({ + wrapper: question_wrapper, + ...question_data + }); + this.questions.push(question) + this.wrapper.appendChild(question_wrapper); + }) + if (data.activity.is_complete) { + this.disable() + let indicator = 'red' + let message = 'Your are not allowed to attempt the quiz again.' + if (data.activity.result == 'Pass') { + indicator = 'green' + message = 'You have already cleared the quiz.' + } + + this.set_quiz_footer(message, indicator, data.activity.score) + } + else { + this.make_actions(); + } + } + + make_actions() { + const button = document.createElement("button"); + button.classList.add("btn", "btn-primary", "mt-5", "mr-2"); + + button.id = 'submit-button'; + button.innerText = 'Submit'; + button.onclick = () => this.submit(); + this.submit_btn = button + this.wrapper.appendChild(button); + } + + submit() { + this.submit_btn.innerText = 'Evaluating..' + this.submit_btn.disabled = true + this.disable() + frappe.call('erpnext.education.utils.evaluate_quiz', { + quiz_name: this.name, + quiz_response: this.get_selected(), + course: this.course + }).then(res => { + this.submit_btn.remove() + if (!res.message) { + frappe.throw("Something went wrong while evaluating the quiz.") + } + + let indicator = 'red' + let message = 'Fail' + if (res.message.status == 'Pass') { + indicator = 'green' + message = 'Congratulations, you cleared the quiz.' + } + + this.set_quiz_footer(message, indicator, res.message.score) + }); + } + + set_quiz_footer(message, indicator, score) { + const div = document.createElement("div"); + div.classList.add("mt-5"); + div.innerHTML = `
+
+

${message}

+
Score: ${score}/100
+
+ +
` + + this.wrapper.appendChild(div) + } + + disable() { + this.questions.forEach(que => que.disable()) + } + + get_selected() { + let que = {} + this.questions.forEach(question => { + que[question.name] = question.get_selected() + }) + return que + } +} + +class Question { + constructor(opts) { + Object.assign(this, opts); + this.make(); + } + + make() { + this.make_question() + this.make_options() + } + + get_selected() { + let selected = this.options.filter(opt => opt.input.checked) + if (this.type == 'Single Correct Answer') { + if (selected[0]) return selected[0].name + } + if (this.type == 'Multiple Correct Answer') { + return selected.map(opt => opt.name) + } + return null + } + + disable() { + let selected = this.options.forEach(opt => opt.input.disabled = true) + } + + make_question() { + let question_wrapper = document.createElement('h5'); + question_wrapper.classList.add('mt-3'); + question_wrapper.innerText = this.question; + this.wrapper.appendChild(question_wrapper); + } + + make_options() { + let make_input = (name, value) => { + let input = document.createElement('input'); + input.id = name; + input.name = this.name; + input.value = value; + input.type = 'radio'; + if (this.type == 'Multiple Correct Answer') + input.type = 'checkbox'; + input.classList.add('form-check-input'); + return input; + } + + let make_label = function(name, value) { + let label = document.createElement('label'); + label.classList.add('form-check-label'); + label.htmlFor = name; + label.innerText = value; + return label + } + + let make_option = function (wrapper, option) { + let option_div = document.createElement('div') + option_div.classList.add('form-check', 'pb-1') + let input = make_input(option.name, option.option); + let label = make_label(option.name, option.option); + option_div.appendChild(input) + option_div.appendChild(label) + wrapper.appendChild(option_div) + return {input: input, ...option} + } + + let options_wrapper = document.createElement('div') + options_wrapper.classList.add('ml-2') + let option_list = [] + this.options.forEach(opt => option_list.push(make_option(options_wrapper, opt))) + this.options = option_list + this.wrapper.appendChild(options_wrapper) + } +} \ No newline at end of file diff --git a/erpnext/www/lms/content.html b/erpnext/www/lms/content.html index a02b2c7500..41f27f3bc1 100644 --- a/erpnext/www/lms/content.html +++ b/erpnext/www/lms/content.html @@ -36,7 +36,7 @@

{{ content.name }} ({{ position + 1 }}/{{length}})

-
+
-
+
{{ content.description }}
{% endmacro %} @@ -95,6 +95,18 @@
{% endmacro %} +{% macro quiz() %} +
+
+
+

{{ content.name }} ({{ position + 1 }}/{{length}})

+
+
+
+
+
+{% endmacro %} + {% block content %}
@@ -104,7 +116,7 @@ {% elif content_type=='Article'%} {{ article() }} {% elif content_type=='Quiz' %} -

Quiz: {{ content.name }}

+ {{ quiz() }} {% endif %}
@@ -113,20 +125,41 @@ {% block script %} {% if content_type=='Video' %} - + + {% elif content_type == 'Quiz' %} + {% endif %} {% endblock %} \ No newline at end of file diff --git a/erpnext/www/lms/content.py b/erpnext/www/lms/content.py index 51a8e32bad..f804cee3bd 100644 --- a/erpnext/www/lms/content.py +++ b/erpnext/www/lms/content.py @@ -27,7 +27,7 @@ def get_context(context): # Set context for content to be displayer - context.content = frappe.get_doc(content_type, content) + context.content = frappe.get_doc(content_type, content).as_dict() context.content_type = content_type context.program = program context.course = course From 8ddb63adae6bbe3835d46019a4522885b4689b9d Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 3 Jun 2019 14:40:52 +0530 Subject: [PATCH 23/40] feat: added auto course enrollment for enrolled programs --- erpnext/education/utils.py | 36 ++++++++++++------------- erpnext/public/js/education/lms/quiz.js | 3 ++- erpnext/www/lms/content.html | 4 ++- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py index 53f02f5f2f..96fb4eed8b 100644 --- a/erpnext/education/utils.py +++ b/erpnext/education/utils.py @@ -179,7 +179,7 @@ def has_super_access(): return bool(roles & {'Administrator', 'Instructor', 'Education Manager', 'System Manager', 'Academic User'}) @frappe.whitelist() -def add_activity(course, content_type, content): +def add_activity(course, content_type, content, program): if has_super_access(): return None @@ -187,18 +187,14 @@ def add_activity(course, content_type, content): if not student: return frappe.throw("Student with email {0} does not exist".format(frappe.session.user), frappe.DoesNotExistError) - course_enrollment = get_enrollment("course", course, student.name) - if not course_enrollment: - return None - - enrollment = frappe.get_doc('Course Enrollment', course_enrollment) + enrollment = get_or_create_course_enrollment(course, program) if content_type == 'Quiz': return else: return enrollment.add_activity(content_type, content) @frappe.whitelist() -def evaluate_quiz(quiz_response, quiz_name, course): +def evaluate_quiz(quiz_response, quiz_name, course, program): import json student = get_current_student() @@ -211,16 +207,12 @@ def evaluate_quiz(quiz_response, quiz_name, course): return {'result': result, 'score': score, 'status': status} if student: - course_enrollment = get_enrollment("course", course, student.name) - if course_enrollment: - enrollment = frappe.get_doc('Course Enrollment', course_enrollment) - if quiz.allowed_attempt(enrollment, quiz_name): - enrollment.add_quiz_activity(quiz_name, quiz_response, result, score, status) - return {'result': result, 'score': score, 'status': status} - else: - return None + enrollment = get_or_create_course_enrollment(course, program) + if quiz.allowed_attempt(enrollment, quiz_name): + enrollment.add_quiz_activity(quiz_name, quiz_response, result, score, status) + return {'result': result, 'score': score, 'status': status} else: - frappe.throw("Something went wrong. Pleae contact the administrator.") + return None @frappe.whitelist() def get_quiz(quiz_name, course): @@ -261,9 +253,17 @@ def create_student_from_current_user(): student.save(ignore_permissions=True) return student -def enroll_in_course(course_name, program_name): +def get_or_create_course_enrollment(course, program): student = get_current_student() - return student.enroll_in_course(course_name=course_name, program_enrollment=get_program_enrollment(program_name)) + course_enrollment = get_enrollment("course", course, student.name) + if not course_enrollment: + program_enrollment = get_enrollment('program', program, student.name) + if not program_enrollment: + frappe.throw("You are not enrolled in program {0}".format(program)) + return + return student.enroll_in_course(course_name=course, program_enrollment=get_enrollment('program', program, student.name)) + else: + return frappe.get_doc('Course Enrollment', course_enrollment) def check_content_completion(content_name, content_type, enrollment_name): activity = frappe.get_all("Course Activity", filters={'enrollment': enrollment_name, 'content_type': content_type, 'content': content_name}) diff --git a/erpnext/public/js/education/lms/quiz.js b/erpnext/public/js/education/lms/quiz.js index f6dc4d08d5..1b520eb9f5 100644 --- a/erpnext/public/js/education/lms/quiz.js +++ b/erpnext/public/js/education/lms/quiz.js @@ -63,7 +63,8 @@ class Quiz { frappe.call('erpnext.education.utils.evaluate_quiz', { quiz_name: this.name, quiz_response: this.get_selected(), - course: this.course + course: this.course, + program: this.program }).then(res => { this.submit_btn.remove() if (!res.message) { diff --git a/erpnext/www/lms/content.html b/erpnext/www/lms/content.html index 41f27f3bc1..acdc54d606 100644 --- a/erpnext/www/lms/content.html +++ b/erpnext/www/lms/content.html @@ -144,6 +144,7 @@ const quiz = new Quiz(document.getElementById('quiz-wrapper'), { name: '{{ content.name }}', course: '{{ course }}', + program: '{{ program }}', quiz_exit_button: quiz_exit_button, next_url: next_url }) @@ -165,7 +166,8 @@ args: { course: "{{ course }}", content_type: "{{ content_type }}", - content: "{{ content.name }}" + content: "{{ content.name }}", + program: "{{ program }}" } } frappe.call(opts).then(res => { From 6d4c66647685aa8f0997286669fa80af5769504e Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 3 Jun 2019 14:41:05 +0530 Subject: [PATCH 24/40] feat: added topic progress api --- erpnext/education/utils.py | 19 +++++++++++++++++++ erpnext/www/lms/course.html | 4 +--- erpnext/www/lms/course.py | 8 +++++++- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py index 96fb4eed8b..3abb840914 100644 --- a/erpnext/education/utils.py +++ b/erpnext/education/utils.py @@ -239,6 +239,25 @@ def get_quiz(quiz_name, course): status, score, result = check_quiz_completion(quiz, course_enrollment) return {'questions': questions, 'activity': {'is_complete': status, 'score': score, 'result': result}} +def get_student_topic_details(topic, course_name, program): + """ + Return the porgress of a course in a program as well as the content to continue from. + :param topic_name: + :param course_name: + """ + student = get_current_student() + course_enrollment = get_or_create_course_enrollment(course_name, program) + progress = student.get_topic_progress(course_enrollment.name, topic) + if not progress: + return {'label':'Open', 'indicator': 'blue'} + count = sum([activity['is_complete'] for activity in progress]) + if count == 0: + return {'label':'Open', 'indicator': 'blue'} + elif count == len(progress): + return {'label':'Completed', 'indicator': 'green'} + elif count < len(progress): + return {'label':'In Progress', 'indicator': 'orange'} + def create_student_from_current_user(): user = frappe.get_doc("User", frappe.session.user) diff --git a/erpnext/www/lms/course.html b/erpnext/www/lms/course.html index ee3b9758cb..199fc169e5 100644 --- a/erpnext/www/lms/course.html +++ b/erpnext/www/lms/course.html @@ -67,9 +67,7 @@
{% if has_access %} {% else %} diff --git a/erpnext/www/lms/course.py b/erpnext/www/lms/course.py index b9aff5c5bd..f59c28cf7e 100644 --- a/erpnext/www/lms/course.py +++ b/erpnext/www/lms/course.py @@ -9,5 +9,11 @@ def get_context(context): course = frappe.get_doc('Course', frappe.form_dict['name']) context.program = frappe.form_dict['program'] context.course = course + context.topics = course.get_topics() - context.has_access = utils.allowed_program_access(context.program) \ No newline at end of file + context.has_access = utils.allowed_program_access(context.program) + context.progress = get_topic_progress(context.topics, course, context.program) + +def get_topic_progress(topics, course, program): + progress = {topic.name: utils.get_student_topic_details(topic, course.name, program) for topic in topics} + return progress From 570161b9789792631fc19f9312769b5bbe777725 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 5 Jun 2019 13:08:53 +0530 Subject: [PATCH 25/40] feat: added course progress apis --- erpnext/education/utils.py | 36 +++++++++++++++++++++++++++++++---- erpnext/www/lms/course.html | 8 +++++++- erpnext/www/lms/program.html | 37 +++++++++++++++++++++++------------- erpnext/www/lms/program.py | 8 +++++++- 4 files changed, 70 insertions(+), 19 deletions(-) diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py index 3abb840914..a35cedc8b0 100644 --- a/erpnext/education/utils.py +++ b/erpnext/education/utils.py @@ -249,14 +249,42 @@ def get_student_topic_details(topic, course_name, program): course_enrollment = get_or_create_course_enrollment(course_name, program) progress = student.get_topic_progress(course_enrollment.name, topic) if not progress: - return {'label':'Open', 'indicator': 'blue'} + return None count = sum([activity['is_complete'] for activity in progress]) if count == 0: - return {'label':'Open', 'indicator': 'blue'} + return {'completed': False, 'started': False} elif count == len(progress): - return {'label':'Completed', 'indicator': 'green'} + return {'completed': True, 'started': True} elif count < len(progress): - return {'label':'In Progress', 'indicator': 'orange'} + return {'completed': False, 'started': True} + +def get_student_course_details(course, program): + """ + Return the porgress of a course in a program as well as the content to continue from. + :param topic_name: + :param course_name: + """ + course_progress = [] + for course_topic in course.topics: + topic = frappe.get_doc("Topic", course_topic.topic) + progress = get_student_topic_details(topic, course.name, program) + if progress: + course_progress.append(progress) + + if course_progress: + number_of_completed_topics = sum([activity['completed'] for activity in course_progress]) + total_topics = len(course_progress) + print("course_progress", course_progress) + print("number_of_completed_topics", number_of_completed_topics) + print("total_topics", total_topics) + if number_of_completed_topics == 0: + return {'completed': False, 'started': False} + if number_of_completed_topics == total_topics: + return {'completed': True, 'started': True} + if number_of_completed_topics < total_topics: + return {'completed': False, 'started': True} + + return None def create_student_from_current_user(): user = frappe.get_doc("User", frappe.session.user) diff --git a/erpnext/www/lms/course.html b/erpnext/www/lms/course.html index 199fc169e5..34158c80fe 100644 --- a/erpnext/www/lms/course.html +++ b/erpnext/www/lms/course.html @@ -67,7 +67,13 @@
{% if has_access %} {% else %} diff --git a/erpnext/www/lms/program.html b/erpnext/www/lms/program.html index d364e5e1d9..0ea2dbb462 100644 --- a/erpnext/www/lms/program.html +++ b/erpnext/www/lms/program.html @@ -34,21 +34,32 @@ {% macro card(course, index, length) %} {% endmacro %} @@ -57,8 +68,8 @@ {{ hero(program.program_name, program.description, has_access) }}
- {% for course in program.courses %} - {{ card(frappe.get_doc("Course", course.course), loop.index, program.courses|length) }} + {% for course in courses %} + {{ card(course, loop.index, courses|length) }} {% endfor %}
diff --git a/erpnext/www/lms/program.py b/erpnext/www/lms/program.py index 4c3a3fdf66..1242336688 100644 --- a/erpnext/www/lms/program.py +++ b/erpnext/www/lms/program.py @@ -7,10 +7,16 @@ no_cache = 1 def get_context(context): context.education_settings = frappe.get_single("Education Settings") context.program = get_program(frappe.form_dict['program']) + context.courses = [frappe.get_doc("Course", course.course) for course in context.program.courses] context.has_access = utils.allowed_program_access(frappe.form_dict['program']) + context.progress = get_course_progress(context.courses, context.program) def get_program(program_name): try: return frappe.get_doc('Program', program_name) except frappe.DoesNotExistError: - frappe.throw(_("Program {0} does not exist.".format(program_name))) \ No newline at end of file + frappe.throw(_("Program {0} does not exist.".format(program_name))) + +def get_course_progress(courses, program): + progress = {course.name: utils.get_student_course_details(course, program) for course in courses} + return progress \ No newline at end of file From 6593263df8de6b0782ea8a9c80e0d1fa79cd139c Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 5 Jun 2019 13:29:51 +0530 Subject: [PATCH 26/40] feat: added navigation --- erpnext/education/utils.py | 2 ++ erpnext/www/lms/course.html | 2 +- erpnext/www/lms/macros/hero.html | 9 +++++++-- erpnext/www/lms/program.html | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py index a35cedc8b0..3352b515f3 100644 --- a/erpnext/education/utils.py +++ b/erpnext/education/utils.py @@ -246,6 +246,8 @@ def get_student_topic_details(topic, course_name, program): :param course_name: """ student = get_current_student() + if not student: + return None course_enrollment = get_or_create_course_enrollment(course_name, program) progress = student.get_topic_progress(course_enrollment.name, topic) if not progress: diff --git a/erpnext/www/lms/course.html b/erpnext/www/lms/course.html index 34158c80fe..1182eb99da 100644 --- a/erpnext/www/lms/course.html +++ b/erpnext/www/lms/course.html @@ -85,7 +85,7 @@ {% block content %}
- {{ hero(course.course_name, course.course_intro, has_access) }} + {{ hero(course.course_name, course.course_intro, has_access, {'name': 'Program', 'url': '/lms/program?program=' + program }) }}
{% for topic in topics %} diff --git a/erpnext/www/lms/macros/hero.html b/erpnext/www/lms/macros/hero.html index 20e9d05c60..dfee93fa99 100644 --- a/erpnext/www/lms/macros/hero.html +++ b/erpnext/www/lms/macros/hero.html @@ -1,6 +1,11 @@ -{% macro hero(title, description, has_access) %} +{% macro hero(title, description, has_access, back) %}
-

{{ title }}

+ +

{{ title }}

{{ description }}

{% if frappe.session.user == 'Guest' %} diff --git a/erpnext/www/lms/program.html b/erpnext/www/lms/program.html index 0ea2dbb462..a0e45e31fe 100644 --- a/erpnext/www/lms/program.html +++ b/erpnext/www/lms/program.html @@ -65,7 +65,7 @@ {% block content %}

- {{ hero(program.program_name, program.description, has_access) }} + {{ hero(program.program_name, program.description, has_access, {'name': 'LMS Home', 'url': '/lms'}) }}
{% for course in courses %} From 16b4129ede4795d8f2af09ce08e60ddbdda97a8b Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 5 Jun 2019 17:29:48 +0530 Subject: [PATCH 27/40] feat: added student profile page --- .../program_enrollment/program_enrollment.py | 23 -------- erpnext/education/utils.py | 53 +++++++++++++++--- erpnext/www/lms/course.py | 2 +- erpnext/www/lms/profile.html | 56 +++++++++++++++++++ erpnext/www/lms/profile.py | 24 ++++++++ erpnext/www/lms/program.py | 2 +- 6 files changed, 128 insertions(+), 32 deletions(-) create mode 100644 erpnext/www/lms/profile.html create mode 100644 erpnext/www/lms/profile.py diff --git a/erpnext/education/doctype/program_enrollment/program_enrollment.py b/erpnext/education/doctype/program_enrollment/program_enrollment.py index 22cca86fcf..d232e47245 100644 --- a/erpnext/education/doctype/program_enrollment/program_enrollment.py +++ b/erpnext/education/doctype/program_enrollment/program_enrollment.py @@ -96,29 +96,6 @@ class ProgramEnrollment(Document): quiz_progress.program = self.program return quiz_progress - def get_program_progress(self): - import math - program = frappe.get_doc("Program", self.program) - program_progress = {} - progress = [] - for course in program.get_all_children(): - course_progress = lms.get_student_course_details(course.course, self.program) - is_complete = False - if course_progress['flag'] == "Completed": - is_complete = True - progress.append({'course_name': course.course_name, 'name': course.course, 'is_complete': is_complete}) - - program_progress['progress'] = progress - program_progress['name'] = self.program - program_progress['program'] = frappe.get_value("Program", self.program, 'program_name') - - try: - program_progress['percentage'] = math.ceil((sum([item['is_complete'] for item in progress] * 100)/len(progress))) - except ZeroDivisionError: - program_progress['percentage'] = 0 - - return program_progress - @frappe.whitelist() def get_program_courses(doctype, txt, searchfield, start, page_len, filters): if filters.get('program'): diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py index 3352b515f3..ac7294badb 100644 --- a/erpnext/education/utils.py +++ b/erpnext/education/utils.py @@ -239,7 +239,7 @@ def get_quiz(quiz_name, course): status, score, result = check_quiz_completion(quiz, course_enrollment) return {'questions': questions, 'activity': {'is_complete': status, 'score': score, 'result': result}} -def get_student_topic_details(topic, course_name, program): +def get_topic_progress(topic, course_name, program): """ Return the porgress of a course in a program as well as the content to continue from. :param topic_name: @@ -260,7 +260,7 @@ def get_student_topic_details(topic, course_name, program): elif count < len(progress): return {'completed': False, 'started': True} -def get_student_course_details(course, program): +def get_course_progress(course, program): """ Return the porgress of a course in a program as well as the content to continue from. :param topic_name: @@ -269,16 +269,14 @@ def get_student_course_details(course, program): course_progress = [] for course_topic in course.topics: topic = frappe.get_doc("Topic", course_topic.topic) - progress = get_student_topic_details(topic, course.name, program) + progress = get_topic_progress(topic, course.name, program) if progress: course_progress.append(progress) - if course_progress: number_of_completed_topics = sum([activity['completed'] for activity in course_progress]) total_topics = len(course_progress) - print("course_progress", course_progress) - print("number_of_completed_topics", number_of_completed_topics) - print("total_topics", total_topics) + if total_topics == 1: + return course_progress[0] if number_of_completed_topics == 0: return {'completed': False, 'started': False} if number_of_completed_topics == total_topics: @@ -288,6 +286,47 @@ def get_student_course_details(course, program): return None +def get_program_progress(program): + program_progress = [] + if not program.courses: + return None + for program_course in program.courses: + course = frappe.get_doc("Course", program_course.course) + progress = get_course_progress(course, program.name) + if progress: + progress['name'] = course.name + progress['course'] = course.course_name + program_progress.append(progress) + + if program_progress: + return program_progress + + return None + +def get_program_completion(program): + topics = frappe.db.sql("""select `tabcourse topic`.topic, `tabcourse topic`.parent + from `tabcourse topic`, + `tabprogram course` + where `tabcourse topic`.parent = `tabprogram course`.course + and `tabprogram course`.parent = '{0}'""".format(program.name)) + + progress = [] + for topic in topics: + topic_doc = frappe.get_doc('Topic', topic[0]) + topic_progress = get_topic_progress(topic_doc, topic[1], program.name) + if topic_progress: + progress.append(topic_progress) + + if progress: + number_of_completed_topics = sum([activity['completed'] for activity in progress if activity]) + total_topics = len(progress) + try: + return int((float(number_of_completed_topics)/total_topics)*100) + except ZeroDivisionError: + return 0 + + return 0 + def create_student_from_current_user(): user = frappe.get_doc("User", frappe.session.user) diff --git a/erpnext/www/lms/course.py b/erpnext/www/lms/course.py index f59c28cf7e..e7ed2e3ed6 100644 --- a/erpnext/www/lms/course.py +++ b/erpnext/www/lms/course.py @@ -15,5 +15,5 @@ def get_context(context): context.progress = get_topic_progress(context.topics, course, context.program) def get_topic_progress(topics, course, program): - progress = {topic.name: utils.get_student_topic_details(topic, course.name, program) for topic in topics} + progress = {topic.name: utils.get_topic_progress(topic, course.name, program) for topic in topics} return progress diff --git a/erpnext/www/lms/profile.html b/erpnext/www/lms/profile.html new file mode 100644 index 0000000000..c642265b63 --- /dev/null +++ b/erpnext/www/lms/profile.html @@ -0,0 +1,56 @@ +{% extends "templates/base.html" %} +{% block title %}Profile{% endblock %} +{% from "www/lms/macros/hero.html" import hero %} + +{% macro card(program) %} + +{% endmacro %} + +{% block content %} +
+
+ +

{{ student.first_name }} {{ student.last_name or '' }}

+

{{ student.name }}

+
+
+
+ {% for program in progress %} + {{ card(program) }} + {% endfor %} +
+
+
+{% endblock %} \ No newline at end of file diff --git a/erpnext/www/lms/profile.py b/erpnext/www/lms/profile.py new file mode 100644 index 0000000000..1a0decce32 --- /dev/null +++ b/erpnext/www/lms/profile.py @@ -0,0 +1,24 @@ +from __future__ import unicode_literals +import erpnext.education.utils as utils +import frappe + +no_cache = 1 + +def get_context(context): + context.student = utils.get_current_student() + context.progress = get_program_progress(context.student.name) + +def get_program_progress(student): + enrolled_programs = frappe.get_all("Program Enrollment", filters={'student':student}, fields=['program']) + student_progress = [] + for list_item in enrolled_programs: + program = frappe.get_doc("Program", list_item.program) + progress = utils.get_program_progress(program) + completion = utils.get_program_completion(program) + student_progress.append({'program': program.program_name, 'name': program.name, 'progress':progress, 'completion': completion}) + + return student_progress + + + + diff --git a/erpnext/www/lms/program.py b/erpnext/www/lms/program.py index 1242336688..a92ec31082 100644 --- a/erpnext/www/lms/program.py +++ b/erpnext/www/lms/program.py @@ -18,5 +18,5 @@ def get_program(program_name): frappe.throw(_("Program {0} does not exist.".format(program_name))) def get_course_progress(courses, program): - progress = {course.name: utils.get_student_course_details(course, program) for course in courses} + progress = {course.name: utils.get_course_progress(course, program) for course in courses} return progress \ No newline at end of file From bd499fcf2730584ef5a0f4d3ee7740dd96738da7 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 5 Jun 2019 17:38:12 +0530 Subject: [PATCH 28/40] chore: added session check for profile page --- erpnext/www/lms/profile.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/erpnext/www/lms/profile.py b/erpnext/www/lms/profile.py index 1a0decce32..4788ea6e70 100644 --- a/erpnext/www/lms/profile.py +++ b/erpnext/www/lms/profile.py @@ -5,7 +5,13 @@ import frappe no_cache = 1 def get_context(context): + if frappe.session.user == "Guest": + frappe.local.flags.redirect_location = '/lms' + raise frappe.Redirect + context.student = utils.get_current_student() + if not context.student: + context.student = frappe.get_doc('User', frappe.session.user) context.progress = get_program_progress(context.student.name) def get_program_progress(student): @@ -17,8 +23,4 @@ def get_program_progress(student): completion = utils.get_program_completion(program) student_progress.append({'program': program.program_name, 'name': program.name, 'progress':progress, 'completion': completion}) - return student_progress - - - - + return student_progress \ No newline at end of file From 7597baab950183fdb3ac5c58eaa8b33b85353240 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 5 Jun 2019 17:49:42 +0530 Subject: [PATCH 29/40] refactor: minor fixes --- erpnext/education/doctype/program/program.json | 5 ++--- erpnext/www/lms/content.html | 4 ++-- erpnext/www/lms/macros/card.html | 2 +- erpnext/www/lms/macros/hero.html | 2 +- erpnext/www/lms/program.html | 4 ++-- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/erpnext/education/doctype/program/program.json b/erpnext/education/doctype/program/program.json index 17a7f9d3c7..a0a2aa2e2b 100644 --- a/erpnext/education/doctype/program/program.json +++ b/erpnext/education/doctype/program/program.json @@ -74,8 +74,7 @@ { "fieldname": "description", "fieldtype": "Small Text", - "label": "Description", - "reqd": 1 + "label": "Description" }, { "fieldname": "intro_video", @@ -113,7 +112,7 @@ "label": "Allow Self Enroll" } ], - "modified": "2019-05-29 14:42:44.693874", + "modified": "2019-06-05 17:47:26.877296", "modified_by": "Administrator", "module": "Education", "name": "Program", diff --git a/erpnext/www/lms/content.html b/erpnext/www/lms/content.html index acdc54d606..92cfec2c8c 100644 --- a/erpnext/www/lms/content.html +++ b/erpnext/www/lms/content.html @@ -38,9 +38,9 @@
{% endblock %} \ No newline at end of file From 2a0483dee3f8d4c5f510126c83416b14769479ea Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 6 Jun 2019 16:02:38 +0530 Subject: [PATCH 40/40] refactor: styling cards and article --- erpnext/www/lms/content.html | 24 ++++++++++++++++++++++-- erpnext/www/lms/course.html | 12 +++++++++--- erpnext/www/lms/index.html | 10 +++++++++- erpnext/www/lms/macros/card.html | 12 ++++++++++-- erpnext/www/lms/program.html | 14 ++++++++++---- erpnext/www/lms/topic.html | 6 ++++++ 6 files changed, 66 insertions(+), 12 deletions(-) diff --git a/erpnext/www/lms/content.html b/erpnext/www/lms/content.html index bdbacc1f3d..9b8c45cb9b 100644 --- a/erpnext/www/lms/content.html +++ b/erpnext/www/lms/content.html @@ -3,6 +3,26 @@ {% block head_include %}