fix: conflicts
This commit is contained in:
@@ -21,7 +21,7 @@ app_license = "AGPL"
|
||||
# include js, css files in header of web template
|
||||
web_include_css = "lms.bundle.css"
|
||||
# web_include_css = "/assets/lms/css/lms.css"
|
||||
web_include_js = "website.bundle.js"
|
||||
web_include_js = ["website.bundle.js", "controls.bundle.js"]
|
||||
|
||||
# include custom scss in every website theme (without file extension ".scss")
|
||||
# website_theme_scss = "lms/public/scss/website"
|
||||
@@ -192,7 +192,10 @@ jinja = {
|
||||
"lms.lms.utils.get_popular_courses",
|
||||
"lms.lms.utils.format_amount",
|
||||
"lms.lms.utils.first_lesson_exists",
|
||||
"lms.lms.utils.has_course_instructor_role"
|
||||
"lms.lms.utils.get_courses_under_review",
|
||||
"lms.lms.utils.has_course_instructor_role",
|
||||
"lms.lms.utils.has_course_moderator_role",
|
||||
"lms.lms.utils.get_certificates"
|
||||
],
|
||||
"filters": []
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "Full Time",
|
||||
"fieldname": "type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Type",
|
||||
@@ -114,7 +115,8 @@
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2022-07-28 13:41:29.224332",
|
||||
"make_attachments_public": 1,
|
||||
"modified": "2022-09-15 17:22:21.662675",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Job",
|
||||
"name": "Job Opportunity",
|
||||
@@ -152,4 +154,4 @@
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "job_title"
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,21 @@ from frappe.model.document import Document
|
||||
from frappe.utils.user import get_system_managers
|
||||
from frappe import _
|
||||
from frappe.utils import get_link_to_form
|
||||
from lms.lms.utils import validate_image
|
||||
|
||||
class JobOpportunity(Document):
|
||||
|
||||
|
||||
def validate(self):
|
||||
self.validate_urls()
|
||||
self.company_logo = validate_image(self.company_logo)
|
||||
|
||||
|
||||
def validate_urls(self):
|
||||
frappe.utils.validate_url(self.company_website, True)
|
||||
frappe.utils.validate_url(self.application_link, True)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def report(job, reason):
|
||||
system_managers = get_system_managers(only_name=True)
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
"docstatus": 0,
|
||||
"doctype": "Web Form",
|
||||
"idx": 0,
|
||||
"is_multi_step_form": 0,
|
||||
"is_standard": 1,
|
||||
"list_columns": [],
|
||||
"login_required": 1,
|
||||
"max_attachment_size": 0,
|
||||
"modified": "2022-02-24 11:31:25.290524",
|
||||
"modified": "2022-09-15 17:22:43.957184",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Job",
|
||||
"name": "job-opportunity",
|
||||
@@ -28,11 +28,9 @@
|
||||
"payment_button_label": "Buy Now",
|
||||
"published": 1,
|
||||
"route": "job-opportunity",
|
||||
"route_to_success_link": 1,
|
||||
"show_attachments": 0,
|
||||
"show_in_grid": 0,
|
||||
"show_list": 1,
|
||||
"show_sidebar": 0,
|
||||
"sidebar_items": [],
|
||||
"success_message": "",
|
||||
"success_url": "/jobs",
|
||||
"title": "Job Opportunity",
|
||||
@@ -63,6 +61,7 @@
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"default": "Full Time",
|
||||
"fieldname": "type",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
"include_in_preview",
|
||||
"index_label",
|
||||
"section_break_6",
|
||||
"youtube",
|
||||
"column_break_9",
|
||||
"quiz_id",
|
||||
"section_break_11",
|
||||
"body",
|
||||
"help_section",
|
||||
"help"
|
||||
@@ -81,11 +85,31 @@
|
||||
"label": "Course",
|
||||
"options": "LMS Course",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"description": "Quiz will appear at the bottom of the lesson.",
|
||||
"fieldname": "quiz_id",
|
||||
"fieldtype": "Data",
|
||||
"label": "Quiz ID"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_9",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_11",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"description": "YouTube Video will appear at the top of the lesson.",
|
||||
"fieldname": "youtube",
|
||||
"fieldtype": "Data",
|
||||
"label": "YouTube Video URL"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2022-05-02 17:16:12.450460",
|
||||
"modified": "2022-09-02 11:30:15.450624",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "Course Lesson",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from ...md import find_macros
|
||||
from lms.lms.utils import get_course_progress, get_lesson_url
|
||||
@@ -11,6 +12,11 @@ from lms.lms.utils import get_course_progress, get_lesson_url
|
||||
class CourseLesson(Document):
|
||||
def validate(self):
|
||||
self.check_and_create_folder()
|
||||
self.validate_quiz_id()
|
||||
|
||||
def validate_quiz_id(self):
|
||||
if self.quiz_id and not frappe.db.exists("LMS Quiz", self.quiz_id):
|
||||
frappe.throw(_("Invalid Quiz ID"))
|
||||
|
||||
def on_update(self):
|
||||
dynamic_documents = ["Exercise", "Quiz"]
|
||||
|
||||
@@ -6,10 +6,6 @@ import unittest
|
||||
from lms.lms.doctype.lms_course.test_lms_course import new_course
|
||||
|
||||
class TestExercise(unittest.TestCase):
|
||||
def setUp(self):
|
||||
frappe.db.sql('delete from `tabLMS Batch Membership`')
|
||||
frappe.db.sql('delete from `tabExercise Submission`')
|
||||
frappe.db.sql('delete from `tabExercise`')
|
||||
|
||||
def new_exercise(self):
|
||||
course = new_course("Test Course")
|
||||
@@ -47,3 +43,8 @@ class TestExercise(unittest.TestCase):
|
||||
user_submission = e.get_user_submission()
|
||||
assert user_submission is not None
|
||||
assert user_submission.name == submission.name
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.sql('delete from `tabLMS Batch Membership`')
|
||||
frappe.db.sql('delete from `tabExercise Submission`')
|
||||
frappe.db.sql('delete from `tabExercise`')
|
||||
|
||||
@@ -6,19 +6,19 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"course",
|
||||
"member_type",
|
||||
"batch",
|
||||
"column_break_3",
|
||||
"member",
|
||||
"member_name",
|
||||
"member_username",
|
||||
"section_break_8",
|
||||
"cohort",
|
||||
"subgroup",
|
||||
"column_break_3",
|
||||
"batch",
|
||||
"current_lesson",
|
||||
"role",
|
||||
"member_section",
|
||||
"member",
|
||||
"member_type",
|
||||
"progress",
|
||||
"column_break_12",
|
||||
"member_name",
|
||||
"member_username"
|
||||
"current_lesson",
|
||||
"progress",
|
||||
"role"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -33,7 +33,8 @@
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Member",
|
||||
"options": "User"
|
||||
"options": "User",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "Student",
|
||||
@@ -70,7 +71,8 @@
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Course",
|
||||
"options": "LMS Course"
|
||||
"options": "LMS Course",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "current_lesson",
|
||||
@@ -103,19 +105,18 @@
|
||||
"label": "Subgroup",
|
||||
"options": "Cohort Subgroup"
|
||||
},
|
||||
{
|
||||
"fieldname": "member_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Member"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_12",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_8",
|
||||
"fieldtype": "Section Break"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2022-03-29 09:47:05.007133",
|
||||
"modified": "2022-09-01 17:11:08.065998",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Batch Membership",
|
||||
|
||||
@@ -3,31 +3,31 @@
|
||||
|
||||
frappe.ui.form.on('LMS Course', {
|
||||
|
||||
onload: function (frm) {
|
||||
onload: function (frm) {
|
||||
|
||||
frm.set_query("chapter", "chapters", function () {
|
||||
return {
|
||||
filters: {
|
||||
"course": frm.doc.name,
|
||||
}
|
||||
};
|
||||
});
|
||||
frm.set_query("chapter", "chapters", function () {
|
||||
return {
|
||||
filters: {
|
||||
"course": frm.doc.name,
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("instructor", function (doc) {
|
||||
return {
|
||||
filters: {
|
||||
"ignore_user_type": 1,
|
||||
}
|
||||
};
|
||||
});
|
||||
frm.set_query("instructor", "instructors", function () {
|
||||
return {
|
||||
filters: {
|
||||
"ignore_user_type": 1,
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("course", "related_courses", function () {
|
||||
return {
|
||||
filters: {
|
||||
"published": true,
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
frm.set_query("course", "related_courses", function () {
|
||||
return {
|
||||
filters: {
|
||||
"published": true,
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
@@ -260,7 +260,8 @@
|
||||
"link_fieldname": "course"
|
||||
}
|
||||
],
|
||||
"modified": "2022-05-19 16:59:21.933367",
|
||||
"make_attachments_public": 1,
|
||||
"modified": "2022-09-14 13:26:53.153822",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Course",
|
||||
|
||||
@@ -5,15 +5,17 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
import json
|
||||
from ...utils import generate_slug
|
||||
from frappe.utils import flt, cint
|
||||
from ...utils import generate_slug, validate_image
|
||||
from frappe.utils import cint
|
||||
from lms.lms.utils import get_chapters
|
||||
|
||||
|
||||
class LMSCourse(Document):
|
||||
|
||||
def validate(self):
|
||||
self.validate_instructors()
|
||||
self.validate_status()
|
||||
self.image = validate_image(self.image)
|
||||
|
||||
def validate_instructors(self):
|
||||
if self.is_new() and not self.instructors:
|
||||
@@ -25,20 +27,22 @@ class LMSCourse(Document):
|
||||
"parenttype": "LMS Course"
|
||||
}).save(ignore_permissions=True)
|
||||
|
||||
|
||||
def validate_status(self):
|
||||
if self.published:
|
||||
self.status = "Approved"
|
||||
|
||||
|
||||
def on_update(self):
|
||||
if not self.upcoming and self.has_value_changed("upcoming"):
|
||||
self.send_email_to_interested_users()
|
||||
|
||||
|
||||
def send_email_to_interested_users(self):
|
||||
interested_users = frappe.get_all("LMS Course Interest",
|
||||
{
|
||||
"course": self.name
|
||||
},
|
||||
["name", "user"])
|
||||
interested_users = frappe.get_all("LMS Course Interest", {
|
||||
"course": self.name
|
||||
},
|
||||
["name", "user"])
|
||||
subject = self.title + " is available!"
|
||||
args = {
|
||||
"title": self.title,
|
||||
@@ -68,6 +72,7 @@ class LMSCourse(Document):
|
||||
def __repr__(self):
|
||||
return f"<Course#{self.name}>"
|
||||
|
||||
|
||||
def has_mentor(self, email):
|
||||
"""Checks if this course has a mentor with given email.
|
||||
"""
|
||||
@@ -77,6 +82,7 @@ class LMSCourse(Document):
|
||||
mapping = frappe.get_all("LMS Course Mentor Mapping", {"course": self.name, "mentor": email})
|
||||
return mapping != []
|
||||
|
||||
|
||||
def add_mentor(self, email):
|
||||
"""Adds a new mentor to the course.
|
||||
"""
|
||||
@@ -97,7 +103,6 @@ class LMSCourse(Document):
|
||||
doc.insert()
|
||||
|
||||
|
||||
|
||||
def get_student_batch(self, email):
|
||||
"""Returns the batch the given student is part of.
|
||||
|
||||
@@ -116,6 +121,7 @@ class LMSCourse(Document):
|
||||
fieldname="batch")
|
||||
return batch_name and frappe.get_doc("LMS Batch", batch_name)
|
||||
|
||||
|
||||
def get_batches(self, mentor=None):
|
||||
batches = frappe.get_all("LMS Batch", {"course": self.name})
|
||||
if mentor:
|
||||
@@ -127,17 +133,21 @@ class LMSCourse(Document):
|
||||
batch_names = {m.batch for m in memberships}
|
||||
return [b for b in batches if b.name in batch_names]
|
||||
|
||||
|
||||
def get_cohorts(self):
|
||||
return frappe.get_all("Cohort", {"course": self.name}, order_by="creation")
|
||||
|
||||
|
||||
def get_cohort(self, cohort_slug):
|
||||
name = frappe.get_value("Cohort", {"course": self.name, "slug": cohort_slug})
|
||||
return name and frappe.get_doc("Cohort", name)
|
||||
|
||||
|
||||
def reindex_exercises(self):
|
||||
for i, c in enumerate(get_chapters(self.name), start=1):
|
||||
self._reindex_exercises_in_chapter(c, i)
|
||||
|
||||
|
||||
def _reindex_exercises_in_chapter(self, c, index):
|
||||
i = 1
|
||||
for lesson in self.get_lessons(c):
|
||||
@@ -147,12 +157,14 @@ class LMSCourse(Document):
|
||||
exercise.save()
|
||||
i += 1
|
||||
|
||||
|
||||
def get_all_memberships(self, member):
|
||||
all_memberships = frappe.get_all("LMS Batch Membership", {"member": member, "course": self.name}, ["batch"])
|
||||
for membership in all_memberships:
|
||||
membership.batch_title = frappe.db.get_value("LMS Batch", membership.batch, "title")
|
||||
return all_memberships
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def reindex_exercises(doc):
|
||||
course_data = json.loads(doc)
|
||||
@@ -160,6 +172,7 @@ def reindex_exercises(doc):
|
||||
course.reindex_exercises()
|
||||
frappe.msgprint("All exercises in this course have been re-indexed.")
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def search_course(text):
|
||||
search_courses = []
|
||||
@@ -185,6 +198,7 @@ def search_course(text):
|
||||
|
||||
return courses
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def submit_for_review(course):
|
||||
chapters = frappe.get_all("Chapter Reference", {"parent": course})
|
||||
@@ -195,7 +209,7 @@ def submit_for_review(course):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def save_course(tags, title, short_introduction, video_link, description, course, image=None):
|
||||
def save_course(tags, title, short_introduction, video_link, description, course, published, upcoming, image=None):
|
||||
if course:
|
||||
doc = frappe.get_doc("LMS Course", course)
|
||||
else:
|
||||
@@ -209,7 +223,9 @@ def save_course(tags, title, short_introduction, video_link, description, course
|
||||
"video_link": video_link,
|
||||
"image": image,
|
||||
"description": description,
|
||||
"tags": tags
|
||||
"tags": tags,
|
||||
"published": cint(published),
|
||||
"upcoming": cint(upcoming)
|
||||
})
|
||||
doc.save(ignore_permissions=True)
|
||||
return doc.name
|
||||
@@ -249,7 +265,7 @@ def save_chapter(course, title, chapter_description, idx, chapter):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def save_lesson(title, body, chapter, preview, idx, lesson):
|
||||
def save_lesson(title, body, chapter, preview, idx, lesson, youtube=None, quiz_id=None):
|
||||
if lesson:
|
||||
doc = frappe.get_doc("Course Lesson", lesson)
|
||||
else:
|
||||
@@ -261,7 +277,9 @@ def save_lesson(title, body, chapter, preview, idx, lesson):
|
||||
"chapter": chapter,
|
||||
"title": title,
|
||||
"body": body,
|
||||
"include_in_preview": preview
|
||||
"include_in_preview": preview,
|
||||
"youtube": youtube,
|
||||
"quiz_id": quiz_id
|
||||
})
|
||||
doc.save(ignore_permissions=True)
|
||||
|
||||
|
||||
@@ -7,13 +7,16 @@ import frappe
|
||||
from .lms_course import LMSCourse
|
||||
import unittest
|
||||
|
||||
|
||||
class TestLMSCourse(unittest.TestCase):
|
||||
|
||||
|
||||
def test_new_course(self):
|
||||
course = new_course("Test Course")
|
||||
assert course.title == "Test Course"
|
||||
assert course.name == "test-course"
|
||||
|
||||
|
||||
# disabled this test as it is failing
|
||||
def _test_add_mentors(self):
|
||||
course = new_course("Test Course")
|
||||
@@ -26,10 +29,23 @@ class TestLMSCourse(unittest.TestCase):
|
||||
mentors_data = [dict(email=mentor.email, batch_count=mentor.batch_count) for mentor in mentors]
|
||||
assert mentors_data == [{"email": "tester@example.com", "batch_count": 0}]
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
if frappe.db.exists("User", "tester@example.com"):
|
||||
frappe.delete_doc("User", "tester@example.com")
|
||||
|
||||
if frappe.db.exists("LMS Course", "test-course"):
|
||||
frappe.db.delete("Exercise Submission", {"course": "test-course"})
|
||||
frappe.db.delete("Exercise Latest Submission", {"course": "test-course"})
|
||||
frappe.db.delete("Exercise", {"course": "test-course"})
|
||||
frappe.db.delete("LMS Batch Membership", {"course": "test-course"})
|
||||
frappe.db.delete("LMS Batch", {"course": "test-course"})
|
||||
frappe.db.delete("LMS Course Mentor Mapping", {"course": "test-course"})
|
||||
frappe.db.delete("Course Instructor", {"parent": "test-course"})
|
||||
frappe.db.sql('delete from `tabCourse Instructor`')
|
||||
frappe.delete_doc("LMS Course", "test-course")
|
||||
|
||||
|
||||
def new_user(name, email):
|
||||
user = frappe.db.exists("User", email)
|
||||
if user:
|
||||
@@ -46,6 +62,7 @@ def new_user(name, email):
|
||||
doc.insert()
|
||||
return doc
|
||||
|
||||
|
||||
def new_course(title, additional_filters=None):
|
||||
course = frappe.db.exists("LMS Course", { "title": title })
|
||||
if course:
|
||||
@@ -66,6 +83,7 @@ def new_course(title, additional_filters=None):
|
||||
doc.insert(ignore_permissions=True)
|
||||
return doc
|
||||
|
||||
|
||||
def create_evaluator():
|
||||
if not frappe.db.exists("Course Evaluator", "evaluator@example.com"):
|
||||
new_user("Evaluator", "evaluator@example.com")
|
||||
|
||||
@@ -7,6 +7,7 @@ from frappe import _
|
||||
|
||||
RE_SLUG_NOTALLOWED = re.compile("[^a-z0-9]+")
|
||||
|
||||
|
||||
def slugify(title, used_slugs=[]):
|
||||
"""Converts title to a slug.
|
||||
|
||||
@@ -59,6 +60,7 @@ def get_membership(course, member, batch=None):
|
||||
membership.batch_title = frappe.db.get_value("LMS Batch", membership.batch, "title")
|
||||
return membership
|
||||
|
||||
|
||||
def get_chapters(course):
|
||||
"""Returns all chapters of this course.
|
||||
"""
|
||||
@@ -85,6 +87,7 @@ def get_lessons(course, chapter=None):
|
||||
|
||||
return lessons
|
||||
|
||||
|
||||
def get_lesson_details(chapter):
|
||||
lessons = []
|
||||
lesson_list = frappe.get_all("Lesson Reference",
|
||||
@@ -94,10 +97,11 @@ def get_lesson_details(chapter):
|
||||
|
||||
for row in lesson_list:
|
||||
lesson_details = frappe.db.get_value("Course Lesson", row.lesson,
|
||||
["name", "title", "include_in_preview", "body", "creation"], as_dict=True)
|
||||
["name", "title", "include_in_preview", "body", "creation", "youtube", "quiz_id"], as_dict=True)
|
||||
lesson_details.number = flt("{}.{}".format(chapter.idx, row.idx))
|
||||
lesson_details.icon = "icon-list"
|
||||
macros = find_macros(lesson_details.body)
|
||||
|
||||
for macro in macros:
|
||||
if macro[0] == "YouTubeVideo":
|
||||
lesson_details.icon = "icon-video"
|
||||
@@ -106,10 +110,12 @@ def get_lesson_details(chapter):
|
||||
lessons.append(lesson_details)
|
||||
return lessons
|
||||
|
||||
|
||||
def get_tags(course):
|
||||
tags = frappe.db.get_value("LMS Course", course, "tags")
|
||||
return tags.split(",") if tags else []
|
||||
|
||||
|
||||
def get_instructors(course):
|
||||
instructor_details = []
|
||||
instructors = frappe.get_all("Course Instructor", {"parent": course},
|
||||
@@ -123,6 +129,7 @@ def get_instructors(course):
|
||||
as_dict=True))
|
||||
return instructor_details
|
||||
|
||||
|
||||
def get_students(course, batch=None):
|
||||
"""Returns (email, full_name, username) of all the students of this batch as a list of dict.
|
||||
"""
|
||||
@@ -137,12 +144,14 @@ def get_students(course, batch=None):
|
||||
filters,
|
||||
["member"])
|
||||
|
||||
|
||||
def get_average_rating(course):
|
||||
ratings = [review.rating for review in get_reviews(course)]
|
||||
if not len(ratings):
|
||||
return None
|
||||
return sum(ratings)/len(ratings)
|
||||
|
||||
|
||||
def get_reviews(course):
|
||||
reviews = frappe.get_all("LMS Course Review",
|
||||
{
|
||||
@@ -164,6 +173,7 @@ def get_reviews(course):
|
||||
|
||||
return reviews
|
||||
|
||||
|
||||
def get_sorted_reviews(course):
|
||||
rating_count = rating_percent = frappe._dict()
|
||||
keys = ["5.0", "4.0", "3.0", "2.0", "1.0"]
|
||||
@@ -180,6 +190,7 @@ def get_sorted_reviews(course):
|
||||
|
||||
return rating_percent
|
||||
|
||||
|
||||
def is_certified(course):
|
||||
certificate = frappe.get_all("LMS Certificate",
|
||||
{
|
||||
@@ -190,6 +201,7 @@ def is_certified(course):
|
||||
return certificate[0].name
|
||||
return
|
||||
|
||||
|
||||
def get_lesson_index(lesson_name):
|
||||
"""Returns the {chapter_index}.{lesson_index} for the lesson.
|
||||
"""
|
||||
@@ -205,6 +217,7 @@ def get_lesson_index(lesson_name):
|
||||
|
||||
return f"{chapter.idx}.{lesson.idx}"
|
||||
|
||||
|
||||
def get_lesson_url(course, lesson_number):
|
||||
if not lesson_number:
|
||||
return
|
||||
@@ -224,8 +237,16 @@ def get_progress(course, lesson):
|
||||
},
|
||||
["status"])
|
||||
|
||||
def render_html(body):
|
||||
return markdown_to_html(body)
|
||||
|
||||
def render_html(body, youtube, quiz_id):
|
||||
if youtube and "/" in youtube:
|
||||
youtube = youtube.split("/")[-1]
|
||||
|
||||
quiz_id = "{{ Quiz('" + quiz_id + "') }}" if quiz_id else ""
|
||||
youtube = "{{ YouTubeVideo('" + youtube + "') }}" if youtube else ""
|
||||
text = youtube + body + quiz_id
|
||||
return markdown_to_html(text)
|
||||
|
||||
|
||||
def is_mentor(course, email):
|
||||
"""Checks if given user is a mentor for this course.
|
||||
@@ -238,6 +259,7 @@ def is_mentor(course, email):
|
||||
"mentor": email
|
||||
})
|
||||
|
||||
|
||||
def is_cohort_staff(course, user_email):
|
||||
"""Returns True if the user is either a mentor or a staff for one or more active cohorts of this course.
|
||||
"""
|
||||
@@ -253,6 +275,7 @@ def is_cohort_staff(course, user_email):
|
||||
}
|
||||
return frappe.db.exists(staff) or frappe.db.exists(mentor)
|
||||
|
||||
|
||||
def get_mentors(course):
|
||||
"""Returns the list of all mentors for this course.
|
||||
"""
|
||||
@@ -269,6 +292,7 @@ def get_mentors(course):
|
||||
course_mentors.append(member)
|
||||
return course_mentors
|
||||
|
||||
|
||||
def is_eligible_to_review(course, membership):
|
||||
""" Checks if user is eligible to review the course """
|
||||
if not membership:
|
||||
@@ -281,6 +305,7 @@ def is_eligible_to_review(course, membership):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def get_course_progress(course, member=None):
|
||||
""" Returns the course progress of the session user """
|
||||
lesson_count = len(get_lessons(course))
|
||||
@@ -295,6 +320,7 @@ def get_course_progress(course, member=None):
|
||||
precision = cint(frappe.db.get_default("float_precision")) or 3
|
||||
return flt(((completed_lessons/lesson_count) * 100), precision)
|
||||
|
||||
|
||||
def get_initial_members(course):
|
||||
members = frappe.get_all("LMS Batch Membership",
|
||||
{
|
||||
@@ -310,12 +336,15 @@ def get_initial_members(course):
|
||||
|
||||
return member_details
|
||||
|
||||
|
||||
def is_instructor(course):
|
||||
return len(list(filter(lambda x: x.name == frappe.session.user, get_instructors(course)))) > 0
|
||||
|
||||
|
||||
def convert_number_to_character(number):
|
||||
return string.ascii_uppercase[number]
|
||||
|
||||
|
||||
def get_signup_optin_checks():
|
||||
|
||||
mapper = frappe._dict({
|
||||
@@ -343,6 +372,7 @@ def get_signup_optin_checks():
|
||||
|
||||
return (", ").join(links)
|
||||
|
||||
|
||||
def get_popular_courses():
|
||||
courses = frappe.get_all("LMS Course", {"published": 1, "upcoming": 0})
|
||||
course_membership = []
|
||||
@@ -356,6 +386,7 @@ def get_popular_courses():
|
||||
course_membership = sorted(course_membership, key = lambda x: x.get("members"), reverse=True)
|
||||
return course_membership[:3]
|
||||
|
||||
|
||||
def get_evaluation_details(course, member=None):
|
||||
info = frappe.db.get_value("LMS Course", course, ["grant_certificate_after", "max_attempts", "duration"], as_dict=True)
|
||||
request = frappe.db.get_value("LMS Certificate Request", {
|
||||
@@ -378,6 +409,7 @@ def get_evaluation_details(course, member=None):
|
||||
"no_of_attempts": no_of_attempts
|
||||
})
|
||||
|
||||
|
||||
def format_amount(amount, currency):
|
||||
amount_reduced = amount / 1000
|
||||
if amount_reduced < 1:
|
||||
@@ -397,13 +429,43 @@ def first_lesson_exists(course):
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def redirect_to_courses_list():
|
||||
frappe.local.flags.redirect_location = "/courses"
|
||||
raise frappe.Redirect
|
||||
|
||||
|
||||
def has_course_instructor_role():
|
||||
def has_course_instructor_role(member=None):
|
||||
return frappe.db.get_value("Has Role", {
|
||||
"parent": frappe.session.user,
|
||||
"parent": member or frappe.session.user,
|
||||
"role": "Course Instructor"
|
||||
}, "name")
|
||||
|
||||
|
||||
def has_course_moderator_role(member=None):
|
||||
return frappe.db.get_value("Has Role", {
|
||||
"parent": member or frappe.session.user,
|
||||
"role": "Course Moderator"
|
||||
}, "name")
|
||||
|
||||
|
||||
def get_courses_under_review():
|
||||
return frappe.get_all("LMS Course", {
|
||||
"status": "Under Review"
|
||||
}, ["name", "upcoming", "title", "image", "enable_certification", "status", "published"]
|
||||
)
|
||||
|
||||
|
||||
def get_certificates(member=None):
|
||||
return frappe.get_all("LMS Certificate", {
|
||||
"member": member or frappe.session.user
|
||||
}, ["course", "member", "issue_date", "expiry_date", "name"])
|
||||
|
||||
|
||||
def validate_image(path):
|
||||
if path and "/private" in path:
|
||||
file = frappe.get_doc("File", {"file_url": path})
|
||||
file.is_private = 0
|
||||
file.save(ignore_permissions=True)
|
||||
return file.file_url
|
||||
return path
|
||||
|
||||
@@ -18,11 +18,11 @@
|
||||
"docstatus": 0,
|
||||
"doctype": "Web Form",
|
||||
"idx": 0,
|
||||
"is_multi_step_form": 0,
|
||||
"is_standard": 1,
|
||||
"list_columns": [],
|
||||
"login_required": 1,
|
||||
"max_attachment_size": 0,
|
||||
"modified": "2022-06-24 19:08:29.197279",
|
||||
"modified": "2022-09-05 13:08:40.071348",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "profile",
|
||||
@@ -30,11 +30,9 @@
|
||||
"payment_button_label": "Buy Now",
|
||||
"published": 1,
|
||||
"route": "edit-profile",
|
||||
"route_to_success_link": 0,
|
||||
"show_attachments": 0,
|
||||
"show_in_grid": 0,
|
||||
"show_list": 0,
|
||||
"show_sidebar": 0,
|
||||
"sidebar_items": [],
|
||||
"success_url": "/profile",
|
||||
"title": "Profile",
|
||||
"web_form_fields": [
|
||||
@@ -50,18 +48,6 @@
|
||||
"reqd": 1,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "middle_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "Middle Name (Optional)",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "last_name",
|
||||
|
||||
@@ -1,27 +1,31 @@
|
||||
{% set enrolled = get_enrolled_courses().in_progress + get_enrolled_courses().completed %}
|
||||
|
||||
|
||||
{% if enrolled | length %}
|
||||
<div class="cards-parent">
|
||||
{% for course in enrolled %}
|
||||
{{ widgets.CourseCard(course=course) }}
|
||||
{% endfor %}
|
||||
{% for course in enrolled %}
|
||||
{{ widgets.CourseCard(course=course) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
|
||||
{% else %}
|
||||
{% set site_name = frappe.db.get_single_value("System Settings", "app_name") %}
|
||||
<div class="empty-state">
|
||||
<div class="empty-state-text">
|
||||
<div class="text-center">
|
||||
<div class="empty-state-heading">{{ _("You haven't enrolled for any courses") }}</div>
|
||||
<div class="course-meta mb-6">{{ _("Here are a few courses we recommend for you to get started with {0}").format(site_name) }}</div>
|
||||
<div class="empty-state p-5">
|
||||
<div style="text-align: left; flex: 1;">
|
||||
<div class="text-center">
|
||||
<div class="empty-state-heading">{{ _("You haven't enrolled for any courses") }}</div>
|
||||
<div class="course-meta mb-6">{{ _("Here are a few courses we recommend for you to get started with {0}").format(site_name) }}</div>
|
||||
</div>
|
||||
{% set recommended_courses = get_popular_courses() %}
|
||||
<div class="cards-parent">
|
||||
{% for course in recommended_courses %}
|
||||
{% if course %}
|
||||
{% set course_details = frappe.get_doc("LMS Course", course.course) %}
|
||||
{{ widgets.CourseCard(course=course_details) }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% set recommended_courses = get_popular_courses() %}
|
||||
<div class="cards-parent">
|
||||
{% for course in recommended_courses %}
|
||||
{% if course %}
|
||||
{% set course_details = frappe.get_doc("LMS Course", course.course) %}
|
||||
{{ widgets.CourseCard(course=course_details) }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="common-card-style course-card" data-course="{{ course.name }}">
|
||||
|
||||
<div class="course-image {% if not course.image %}default-image{% endif %}" {% if course.image %}
|
||||
style="background-image: url( {{ course.image }} );" {% endif %}>
|
||||
style="background-image: url( {{ course.image | urlencode }} );" {% endif %}>
|
||||
<div class="course-tags">
|
||||
{% for tag in get_tags(course.name) %}
|
||||
<div class="course-card-pills">{{ tag }}</div>
|
||||
@@ -90,10 +90,12 @@
|
||||
<a class="button-links" href="{{ get_profile_url(instructors[0].username) }}">
|
||||
<span class="course-instructor">
|
||||
{% if ins_len == 1 %}
|
||||
{{ instructors[0].full_name }}
|
||||
{{ instructors[0].full_name }}
|
||||
{% elif ins_len == 2 %}
|
||||
{{ instructors[0].full_name.split(" ")[0] }} and {{ instructors[1].full_name.split(" ")[0] }}
|
||||
{% else %}
|
||||
{% set suffix = "other" if ins_len - 1 == 1 else "others" %}
|
||||
{{ instructors[0].full_name.split(" ")[0] }} and {{ ins_len - 1 }} {{ suffix }}
|
||||
{% set suffix = "other" if ins_len - 1 == 1 else "others" %}
|
||||
{{ instructors[0].full_name.split(" ")[0] }} and {{ ins_len - 1 }} {{ suffix }}
|
||||
{% endif %}
|
||||
</span>
|
||||
</a>
|
||||
|
||||
@@ -1,18 +1,28 @@
|
||||
<div class="course-home-outline">
|
||||
|
||||
{% set chapters = get_chapters(course.name) %}
|
||||
|
||||
{% if course.edit_mode and course.name %}
|
||||
<button class="btn btn-md btn-secondary btn-chapter pull-right"> {{ _("New Chapter") }} </button>
|
||||
{% endif %}
|
||||
|
||||
{% if course.name and (course.edit_mode or get_chapters(course.name) | length) %}
|
||||
{% if course.name and (course.edit_mode or chapters | length) %}
|
||||
<div class="course-home-headings" id="outline-heading">
|
||||
{{ _("Course Content") }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if get_chapters(course.name) | length %}
|
||||
{% if course.edit_mode and course.name and not chapters | length %}
|
||||
<div class="chapter-parent chapter-edit new-chapter">
|
||||
<div contenteditable="true" data-placeholder="{{ _('Chapter Name') }}" class="chapter-title-main"></div>
|
||||
<div class="chapter-description small my-2" contenteditable="true" data-placeholder="{{ _('Short Description') }}"></div>
|
||||
<button class="btn btn-sm btn-secondary d-block btn-save-chapter" data-index="1"> {{ _('Save') }} </button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% for chapter in get_chapters(course.name) %}
|
||||
{% if chapters | length %}
|
||||
|
||||
{% for chapter in chapters %}
|
||||
<div class="chapter-parent {% if course.edit_mode %} chapter-edit {% endif %} ">
|
||||
<div class="chapter-title" {% if not course.edit_mode %} data-toggle="collapse" aria-expanded="false"
|
||||
data-target="#{{ get_slugified_chapter_title(chapter.title) }}" {% endif %} >
|
||||
@@ -55,7 +65,7 @@
|
||||
{% set active = membership.current_lesson == lesson.name %}
|
||||
<div class="lesson-info {% if active and not course.edit_mode %} active-lesson {% endif %}">
|
||||
|
||||
{% if membership or lesson.include_in_preview or is_instructor %}
|
||||
{% if membership or lesson.include_in_preview or is_instructor or has_course_moderator_role() %}
|
||||
<a class="lesson-links" data-course="{{ course.name }}"
|
||||
{% if is_instructor and not lesson.include_in_preview %}
|
||||
title="{{ _('This lesson is not available for preview. As you are the Instructor of the course only you can see it.') }}"
|
||||
@@ -185,3 +195,4 @@ const show_no_preview_dialog = (e) => {
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<div> {{ member.headline }} </div>
|
||||
{% endif %}
|
||||
|
||||
{% set course_count = get_authored_courses(member.name) | length %}
|
||||
{% set course_count = get_authored_courses(member.name, True) | length %}
|
||||
{% if show_course_count and course_count > 0 %}
|
||||
{% set suffix = "Courses" if course_count > 1 else "Course" %}
|
||||
<div class="">
|
||||
|
||||
@@ -1,166 +1,169 @@
|
||||
{% if not course.upcoming %}
|
||||
<div class="reviews-parent">
|
||||
{% set reviews = get_reviews(course.name) %}
|
||||
<div class="mb-5">
|
||||
<span class="course-home-headings"> {{ _("Reviews") }} </span>
|
||||
{% if is_eligible_to_review(course.name, membership) and reviews | length %}
|
||||
<span class="review-link button is-secondary pull-right">
|
||||
{{ _("Write a review") }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% set avg_rating = get_average_rating(course.name) %}
|
||||
{% if avg_rating %}
|
||||
<div class="reviews-header">
|
||||
<div class="text-center">
|
||||
<div class="avg-rating"> {{ frappe.utils.flt(avg_rating, frappe.get_system_settings("float_precision") or 3) }} </div>
|
||||
<div class="course-meta"> {{ reviews | length }} {{ _("ratings") }} </div>
|
||||
<div class="avg-rating-stars">
|
||||
<div class="rating">
|
||||
{% for i in [1, 2, 3, 4, 5] %}
|
||||
<svg class="icon icon-md {% if i <= frappe.utils.ceil(avg_rating) %} star-click {% endif %}" data-rating="{{ i }}">
|
||||
<use href="#icon-star"></use>
|
||||
</svg>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="course-meta">
|
||||
{{ frappe.utils.flt(avg_rating, frappe.get_system_settings("float_precision") or 3) }} {{ _("out of 5 ") }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="vertical-divider"></div>
|
||||
{% set sorted_reviews = get_sorted_reviews(course.name) %}
|
||||
<div>
|
||||
{% for review in sorted_reviews %}
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div class="course-meta mr-2"> {{ frappe.utils.cint(review) }} {{ _("stars") }} </div>
|
||||
<div class="progress">
|
||||
<div class="progress-bar" role="progressbar" aria-valuenow="{{ sorted_reviews[review] }}"
|
||||
aria-valuemin="0" aria-valuemax="100" style="width:{{ sorted_reviews[review] }}%">
|
||||
<span class="sr-only"> {{ sorted_reviews[review] }} Complete</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="course-meta ml-3"> {{ frappe.utils.cint(sorted_reviews[review]) }}% </div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if reviews | length %}
|
||||
<div class="mt-12">
|
||||
{% for review in reviews %}
|
||||
<div class="mb-4">
|
||||
<div class="d-flex align-items-center">
|
||||
|
||||
<div class="mr-4">
|
||||
{{ widgets.Avatar(member=review.owner_details, avatar_class="avatar-medium") }}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="d-flex align-items-center">
|
||||
<a class="button-links mr-4" href="{{get_profile_url(review.owner_details.username) }}">
|
||||
<span class="bold-heading">
|
||||
{{ review.owner_details.full_name }}
|
||||
</span>
|
||||
</a>
|
||||
<div class="frappe-timestamp course-meta" data-timestamp="{{ review.creation }}"> frappe.utils.pretty_date(review.creation) </div>
|
||||
</div>
|
||||
|
||||
<div class="rating">
|
||||
{% for i in [1, 2, 3, 4, 5] %}
|
||||
<svg class="icon icon-md {% if i <= review.rating %} star-click {% endif %}" data-rating="{{ i }}">
|
||||
<use href="#icon-star"></use>
|
||||
</svg>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="review-content"> {{ review.review }} </div>
|
||||
</div>
|
||||
{% if loop.index != reviews | length %}
|
||||
<div class="card-divider"></div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<div class="empty-state">
|
||||
<div>
|
||||
<img class="icon icon-xl" src="/assets/lms/icons/comment.svg">
|
||||
</div>
|
||||
<div class="empty-state-text">
|
||||
<div class="empty-state-heading">{{ _("Review the course") }}</div>
|
||||
<div class="course-meta">{{ _("Help us improve our course material.") }}</div>
|
||||
</div>
|
||||
<div>
|
||||
{% if not is_instructor(course.name) %}
|
||||
{% set reviews = get_reviews(course.name) %}
|
||||
<div class="mb-5">
|
||||
<span class="course-home-headings"> {{ _("Reviews") }} </span>
|
||||
{% if is_eligible_to_review(course.name, membership) %}
|
||||
<span class="review-link button is-secondary">
|
||||
{{ _("Write a review") }}
|
||||
</span>
|
||||
{% elif frappe.session.user == "Guest" %}
|
||||
<a class="button is-primary" href="/login?redirect-to=/courses/{{ course.name }}"> {{ _("Login") }} </a>
|
||||
{% elif not membership %}
|
||||
<div class="button is-primary join-batch" data-course="{{ course.name | urlencode }}"> {{ _("Start Learning") }} </div>
|
||||
<span class="btn btn-secondary btn-sm review-link">
|
||||
{{ _("Write a review") }}
|
||||
</span>
|
||||
{% elif not is_instructor(course.name) and frappe.session.user == "Guest" %}
|
||||
<a class="btn btn-secondary btn-s pull-rightm" href="/login?redirect-to=/courses/{{ course.name }}"> {{ _("Login") }} </a>
|
||||
{% elif not is_instructor(course.name) and not membership and course.status == "Approved" %}
|
||||
<div class="btn btn-secondary btn-sm join-batch pull-right" data-course="{{ course.name | urlencode }}"> {{ _("Start Learning") }} </div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% set avg_rating = get_average_rating(course.name) %}
|
||||
{% if avg_rating %}
|
||||
<div class="reviews-header">
|
||||
<div class="text-center">
|
||||
<div class="avg-rating">
|
||||
{{ frappe.utils.flt(avg_rating, frappe.get_system_settings("float_precision") or 3) }}
|
||||
</div>
|
||||
<div class="course-meta"> {{ reviews | length }} {{ _("ratings") }} </div>
|
||||
<div class="avg-rating-stars">
|
||||
<div class="rating">
|
||||
{% for i in [1, 2, 3, 4, 5] %}
|
||||
<svg class="icon icon-md {% if i <= frappe.utils.ceil(avg_rating) %} star-click {% endif %}" data-rating="{{ i }}">
|
||||
<use href="#icon-star"></use>
|
||||
</svg>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="course-meta">
|
||||
{{ frappe.utils.flt(avg_rating, frappe.get_system_settings("float_precision") or 3) }} {{ _("out of 5 ") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="vertical-divider"></div>
|
||||
{% set sorted_reviews = get_sorted_reviews(course.name) %}
|
||||
<div>
|
||||
{% for review in sorted_reviews %}
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div class="course-meta mr-2">
|
||||
{{ frappe.utils.cint(review) }} {{ _("stars") }}
|
||||
</div>
|
||||
<div class="progress">
|
||||
<div class="progress-bar" role="progressbar" aria-valuenow="{{ sorted_reviews[review] }}"
|
||||
aria-valuemin="0" aria-valuemax="100" style="width:{{ sorted_reviews[review] }}%">
|
||||
<span class="sr-only"> {{ sorted_reviews[review] }} Complete</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="course-meta ml-3"> {{ frappe.utils.cint(sorted_reviews[review]) }}% </div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if reviews | length %}
|
||||
<div class="mt-12">
|
||||
{% for review in reviews %}
|
||||
<div class="mb-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="mr-4">
|
||||
{{ widgets.Avatar(member=review.owner_details, avatar_class="avatar-medium") }}
|
||||
</div>
|
||||
<div>
|
||||
<div class="d-flex align-items-center">
|
||||
<a class="button-links mr-4" href="{{get_profile_url(review.owner_details.username) }}">
|
||||
<span class="bold-heading">
|
||||
{{ review.owner_details.full_name }}
|
||||
</span>
|
||||
</a>
|
||||
<div class="frappe-timestamp course-meta" data-timestamp="{{ review.creation }}">
|
||||
{{ frappe.utils.pretty_date(review.creation) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="rating">
|
||||
{% for i in [1, 2, 3, 4, 5] %}
|
||||
<svg class="icon icon-md {% if i <= review.rating %} star-click {% endif %}" data-rating="{{ i }}">
|
||||
<use href="#icon-star"></use>
|
||||
</svg>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="review-content"> {{ review.review }} </div>
|
||||
</div>
|
||||
{% if loop.index != reviews | length %}
|
||||
<div class="card-divider"></div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
|
||||
{% else %}
|
||||
<div class="empty-state">
|
||||
<img class="icon icon-xl" src="/assets/lms/icons/comment.svg">
|
||||
<div class="empty-state-text">
|
||||
<div class="empty-state-heading">{{ _("Review the course") }}</div>
|
||||
<div class="course-meta">{{ _("Help us improve our course material.") }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="modal fade review-modal" id="review-modal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<div class="font-weight-bold">{{ _("Write a review") }}</div>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="review-form" id="review-form">
|
||||
<div class="form-group">
|
||||
<div class="clearfix">
|
||||
<label class="control-label reqd" style="padding-right: 0px;">{{ _("Rating") }}</label>
|
||||
</div>
|
||||
<div class="control-input-wrapper">
|
||||
<div class="control-input">
|
||||
<div class="rating rating-field" id="rating">
|
||||
{% for i in [1, 2, 3, 4, 5] %}
|
||||
<svg class="icon icon-md icon-rating" data-rating="{{ i }}">
|
||||
<use href="#icon-star"></use>
|
||||
</svg>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="clearfix">
|
||||
<label class="control-label reqd" style="padding-right: 0px;">{{ _("Review") }}</label>
|
||||
<div class="modal fade review-modal" id="review-modal" tabindex="-1" role="dialog"
|
||||
aria-labelledby="exampleModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<div class="font-weight-bold">{{ _("Write a review") }}</div>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="control-input-wrapper">
|
||||
<div class="control-input">
|
||||
<textarea type="text" autocomplete="off" class="input-with-feedback form-control review-field"
|
||||
data-fieldtype="Text" data-fieldname="feedback_comments" placeholder="" style="height: 300px;"
|
||||
spellcheck="false"></textarea>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal-body">
|
||||
<form class="review-form" id="review-form">
|
||||
<div class="form-group">
|
||||
<div class="clearfix">
|
||||
<label class="control-label reqd" style="padding-right: 0px;">{{ _("Rating") }}</label>
|
||||
</div>
|
||||
<div class="control-input-wrapper">
|
||||
<div class="control-input">
|
||||
<div class="rating rating-field" id="rating">
|
||||
{% for i in [1, 2, 3, 4, 5] %}
|
||||
<svg class="icon icon-md icon-rating" data-rating="{{ i }}">
|
||||
<use href="#icon-star"></use>
|
||||
</svg>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<div class="clearfix">
|
||||
<label class="control-label reqd" style="padding-right: 0px;">{{ _("Review") }}</label>
|
||||
</div>
|
||||
<div class="control-input-wrapper">
|
||||
<div class="control-input">
|
||||
<textarea type="text" autocomplete="off" class="input-with-feedback form-control review-field"
|
||||
data-fieldtype="Text" data-fieldname="feedback_comments" placeholder="" style="height: 300px;"
|
||||
spellcheck="false"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="error-field muted-text"></p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<p class="error-field muted-text"></p>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="button submit-review is-primary" data-course="{{ course.name | urlencode}}" id="submit-review">
|
||||
{{ _("Submit") }}</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal-footer">
|
||||
<div class="button submit-review is-primary" data-course="{{ course.name | urlencode}}" id="submit-review">
|
||||
{{ _("Submit") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -6,17 +6,22 @@ import random
|
||||
import re
|
||||
from frappe import _
|
||||
from frappe.website.utils import is_signup_disabled
|
||||
from lms.lms.utils import validate_image
|
||||
import requests
|
||||
from frappe.geo.country_info import get_all
|
||||
from lms.widgets import Widgets
|
||||
|
||||
|
||||
class CustomUser(User):
|
||||
|
||||
|
||||
def validate(self):
|
||||
super(CustomUser, self).validate()
|
||||
self.validate_username_characters()
|
||||
self.validate_skills()
|
||||
self.validate_completion()
|
||||
self.user_image = validate_image(self.user_image)
|
||||
self.cover_image = validate_image(self.cover_image)
|
||||
|
||||
|
||||
def validate_username_characters(self):
|
||||
if len(self.username):
|
||||
@@ -127,7 +132,8 @@ class CustomUser(User):
|
||||
def get_enrolled_courses():
|
||||
in_progress = []
|
||||
completed = []
|
||||
memberships = get_course_membership(frappe.session.user, member_type="Student")
|
||||
memberships = get_course_membership(None, member_type="Student")
|
||||
|
||||
for membership in memberships:
|
||||
course = frappe.db.get_value("LMS Course", membership.course, ["name", "upcoming", "title", "image",
|
||||
"enable_certification", "paid_certificate", "price_certificate", "currency", "published"], as_dict=True)
|
||||
@@ -144,10 +150,11 @@ def get_enrolled_courses():
|
||||
"completed": completed
|
||||
}
|
||||
|
||||
def get_course_membership(member, member_type=None):
|
||||
""" Returns all memberships of the user """
|
||||
def get_course_membership(member=None, member_type=None):
|
||||
""" Returns all memberships of the user. """
|
||||
|
||||
filters = {
|
||||
"member": member
|
||||
"member": member or frappe.session.user
|
||||
}
|
||||
if member_type:
|
||||
filters["member_type"] = member_type
|
||||
@@ -155,24 +162,24 @@ def get_course_membership(member, member_type=None):
|
||||
return frappe.get_all("LMS Batch Membership", filters, ["name", "course", "progress"])
|
||||
|
||||
|
||||
def get_authored_courses(member, only_published=True):
|
||||
"""Returns the number of courses authored by this user.
|
||||
"""
|
||||
def get_authored_courses(member=None, only_published=True):
|
||||
""" Returns the number of courses authored by this user. """
|
||||
course_details = []
|
||||
|
||||
filters = {
|
||||
"instructor": member
|
||||
}
|
||||
if only_published:
|
||||
filters["published"] = True
|
||||
courses = frappe.get_all('LMS Course', filters)
|
||||
courses = frappe.get_all("Course Instructor", {
|
||||
"instructor": member or frappe.session.user
|
||||
}, ["parent"])
|
||||
|
||||
for course in courses:
|
||||
course_details.append(frappe.db.get_value("LMS Course", course,
|
||||
["name", "upcoming", "title", "image", "enable_certification", "status"], as_dict=True))
|
||||
detail = frappe.db.get_value("LMS Course", course.parent,
|
||||
["name", "upcoming", "title", "image", "enable_certification", "status", "published"], as_dict=True)
|
||||
|
||||
if only_published and detail and not detail.published:
|
||||
continue
|
||||
course_details.append(detail)
|
||||
|
||||
return course_details
|
||||
|
||||
|
||||
def get_palette(full_name):
|
||||
"""
|
||||
Returns a color unique to each member for Avatar """
|
||||
@@ -328,3 +335,22 @@ def get_users(or_filters, start, page_length, text):
|
||||
""".format(or_filters = or_filters, start=start, page_length=page_length), as_dict=1)
|
||||
|
||||
return users
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def save_role(user, role, value):
|
||||
if cint(value):
|
||||
doc = frappe.get_doc({
|
||||
"doctype": "Has Role",
|
||||
"parent": user,
|
||||
"role": role,
|
||||
"parenttype": "User",
|
||||
"parentfield": "roles"
|
||||
})
|
||||
doc.save(ignore_permissions=True)
|
||||
else:
|
||||
frappe.db.delete("Has Role", {
|
||||
"parent": user,
|
||||
"role": role
|
||||
})
|
||||
return True
|
||||
|
||||
@@ -30,4 +30,5 @@ lms.patches.v0_0.move_certification_to_certificate
|
||||
lms.patches.v0_0.quiz_submission_member
|
||||
lms.patches.v0_0.delete_old_module_docs #08-07-2022
|
||||
lms.patches.v0_0.delete_course_web_forms #21-08-2022
|
||||
lms.patches.v0_0.create_course_instructor_role
|
||||
lms.patches.v0_0.create_course_instructor_role #29-08-2022
|
||||
lms.patches.v0_0.create_course_moderator_role
|
||||
|
||||
@@ -6,5 +6,6 @@ def execute():
|
||||
"doctype": "Role",
|
||||
"role_name": "Course Instructor",
|
||||
"home_page": "/dashboard",
|
||||
"desk_access": 0
|
||||
})
|
||||
role.save(ignore_permissions=True)
|
||||
|
||||
11
lms/patches/v0_0/create_course_moderator_role.py
Normal file
11
lms/patches/v0_0/create_course_moderator_role.py
Normal file
@@ -0,0 +1,11 @@
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
if not frappe.db.exists("Role", "Course Moderator"):
|
||||
role = frappe.get_doc({
|
||||
"doctype": "Role",
|
||||
"role_name": "Course Moderator",
|
||||
"home_page": "/dashboard",
|
||||
"desk_access": 0
|
||||
})
|
||||
role.save(ignore_permissions=True)
|
||||
@@ -44,37 +44,36 @@ input[type=checkbox] {
|
||||
}
|
||||
|
||||
.course-image .course-tags {
|
||||
width: 95%;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.course-card-pills {
|
||||
background: #ffffff;
|
||||
margin-left: 0;
|
||||
margin-right: 1rem;
|
||||
border-radius: var(--border-radius);
|
||||
padding: 3.5px 8px;
|
||||
font-size: 11px;
|
||||
text-align: center;
|
||||
letter-spacing: 0.011em;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
color: var(--gray-900);
|
||||
width: fit-content;
|
||||
box-shadow: var(--shadow-sm);
|
||||
background: #ffffff;
|
||||
margin-left: 0;
|
||||
margin-right: 1rem;
|
||||
border-radius: var(--border-radius);
|
||||
padding: 3.5px 8px;
|
||||
font-size: 11px;
|
||||
text-align: center;
|
||||
letter-spacing: 0.011em;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
color: var(--gray-900);
|
||||
width: fit-content;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.dark-pills {
|
||||
background: rgba(25, 39, 52, 0.8);
|
||||
color: #ffffff;
|
||||
background: rgba(25, 39, 52, 0.8);
|
||||
color: #ffffff;
|
||||
}
|
||||
.dark-pills img {
|
||||
width: 0.75rem;
|
||||
height: 0.75rem;
|
||||
width: 0.75rem;
|
||||
height: 0.75rem;
|
||||
}
|
||||
|
||||
.common-page-style {
|
||||
padding: 2rem 0 5rem;
|
||||
min-height: 60vh;
|
||||
padding-top: 3rem;
|
||||
background-color: var(--bg-color);
|
||||
}
|
||||
@@ -126,9 +125,10 @@ input[type=checkbox] {
|
||||
}
|
||||
|
||||
.course-card-title {
|
||||
font-weight: 600;
|
||||
color: var(--gray-900);
|
||||
margin-bottom: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: var(--gray-900);
|
||||
margin-bottom: 1.25rem;
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
.card-divider {
|
||||
@@ -661,7 +661,7 @@ input[type=checkbox] {
|
||||
font-size: var(--text-sm);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 3rem;
|
||||
margin-bottom: 2.5rem;
|
||||
padding-left: 200px;
|
||||
padding-right: 1rem;
|
||||
box-shadow: var(--shadow-sm);
|
||||
@@ -764,7 +764,7 @@ input[type=checkbox] {
|
||||
}
|
||||
|
||||
.education-details {
|
||||
margin-top: 3rem;
|
||||
margin-top: 1.25rem;
|
||||
}
|
||||
|
||||
.bold-title {
|
||||
@@ -895,24 +895,22 @@ pre {
|
||||
}
|
||||
}
|
||||
|
||||
.profile-card {
|
||||
flex-direction: column;
|
||||
padding: 1rem 1.25rem;
|
||||
.column-card {
|
||||
flex-direction: column;
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
background: var(--gray-200);
|
||||
border-radius: var(--border-radius-lg);
|
||||
padding: 2rem;
|
||||
padding: 4rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.empty-state-text {
|
||||
flex: 1;
|
||||
margin-left: 1.25rem;
|
||||
text-align: center;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.empty-state-heading {
|
||||
@@ -1400,19 +1398,25 @@ pre {
|
||||
overflow: inherit;
|
||||
}
|
||||
|
||||
.dashboard .nav-link {
|
||||
.lms-nav .nav-link {
|
||||
color: var(--text-muted);
|
||||
padding: 0 0 var(--padding-md);
|
||||
margin-right: var(--margin-xl);
|
||||
padding: var(--padding-md) 0;
|
||||
margin: 0 var(--margin-md);
|
||||
}
|
||||
|
||||
.dashboard .nav-link.active {
|
||||
.lms-nav .nav-link.active {
|
||||
font-weight: 600;
|
||||
border-bottom: 1px solid var(--primary);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.dashboard .nav-link:hover {
|
||||
@media (min-width: 500px) {
|
||||
.lms-nav .nav-item:first-child .nav-link {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.lms-nav .nav-link:hover {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
@@ -1541,7 +1545,7 @@ li {
|
||||
}
|
||||
}
|
||||
|
||||
[contenteditable] {
|
||||
[contenteditable="true"] {
|
||||
outline: none;
|
||||
background-color: var(--bg-light-gray);
|
||||
border-radius: var(--border-radius);
|
||||
@@ -1550,7 +1554,7 @@ li {
|
||||
color: var(--gray-900);
|
||||
}
|
||||
|
||||
[contenteditable]:empty:before {
|
||||
[contenteditable="true"]:empty:before {
|
||||
content: attr(data-placeholder);
|
||||
color: var(--gray-600);
|
||||
}
|
||||
@@ -1588,7 +1592,7 @@ li {
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.course-card-pills[contenteditable] {
|
||||
.course-card-pills[contenteditable="true"] {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
@@ -1658,6 +1662,49 @@ li {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.help-article {
|
||||
.medium {
|
||||
font-size: var(--text-base);
|
||||
}
|
||||
|
||||
.quiz-row {
|
||||
position: relative;
|
||||
color: var(--text-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.course-creation-link {
|
||||
float: right;
|
||||
}
|
||||
|
||||
@media (max-width: 500px) {
|
||||
.course-creation-link {
|
||||
float: inherit;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.indicator-pill::before {
|
||||
width: 0;
|
||||
height: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.review-link {
|
||||
float: right
|
||||
}
|
||||
|
||||
.role {
|
||||
margin-bottom: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media (min-width: 500px) {
|
||||
.role:last-child {
|
||||
margin-left: 5rem
|
||||
}
|
||||
}
|
||||
|
||||
.icon-xl {
|
||||
width: 2.75rem;
|
||||
height: 2.75rem;
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ const join_course = (e) => {
|
||||
}, 3);
|
||||
setTimeout(function () {
|
||||
window.location.href = `/courses/${course}/learn/1.1`;
|
||||
}, 3000);
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -127,7 +127,7 @@ const scroll_to_chapter_container = () => {
|
||||
scrollTop: $(".new-chapter").offset().top
|
||||
}, 1000);
|
||||
$(".new-chapter").find(".chapter-title-main").focus();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const save_chapter = (e) => {
|
||||
@@ -150,7 +150,7 @@ const save_chapter = (e) => {
|
||||
});
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000)
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
45
lms/subscription_utils.py
Normal file
45
lms/subscription_utils.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import frappe
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def get_add_on_details(plan: str) -> dict[str, int]:
|
||||
"""
|
||||
Returns the number of courses and course members to be billed under add-ons for SAAS subscription
|
||||
"""
|
||||
|
||||
return {
|
||||
"courses": get_add_on_courses(plan),
|
||||
"members": get_add_on_members(plan)
|
||||
}
|
||||
|
||||
|
||||
def get_published_courses() -> int:
|
||||
return frappe.db.count("LMS Course", {"published": 1})
|
||||
|
||||
|
||||
def get_add_on_courses(plan: str) -> int:
|
||||
COURSE_LIMITS = {"Lite": 5, "Pro": 20}
|
||||
add_on_courses = 0
|
||||
courses_included_in_plans = COURSE_LIMITS.get(plan)
|
||||
|
||||
if courses_included_in_plans:
|
||||
published_courses = get_published_courses()
|
||||
add_on_courses = published_courses - courses_included_in_plans if published_courses > courses_included_in_plans else 0
|
||||
|
||||
return add_on_courses
|
||||
|
||||
|
||||
def get_add_on_members(plan: str) -> int:
|
||||
MEMBER_LIMITS = {"Lite": 100, "Pro": 500}
|
||||
add_on_members = 0
|
||||
members_included_in_plans = MEMBER_LIMITS.get(plan)
|
||||
|
||||
if members_included_in_plans:
|
||||
active_members = get_members()
|
||||
add_on_members = active_members - members_included_in_plans if active_members > members_included_in_plans else 0
|
||||
|
||||
return add_on_members
|
||||
|
||||
|
||||
def get_members() -> int:
|
||||
return frappe.db.count("LMS Batch Membership")
|
||||
@@ -1,38 +1,44 @@
|
||||
|
||||
<div id="certificate-card" style="background: #FFFFFF; border-radius: 0.5rem; position: relative;
|
||||
box-shadow: 0px 1px 2px rgba(25, 39, 52, 0.05), 0px 0px 4px rgba(25, 39, 52, 0.1); padding: 1rem;">
|
||||
|
||||
<div style="border: 10px solid var(--primary-color); display: flex; flex-direction: column; align-items: center; padding: 4rem;
|
||||
justify-content: center; background-color: #FFFFFF;">
|
||||
|
||||
<img src="{{ logo }}" style="height: 1.5rem;">
|
||||
<div style="margin-top: 4rem;">
|
||||
{{ _("This certifies that") }}
|
||||
</div>
|
||||
<div style="font-size: 2rem; font-weight: 500; color: #192734;"> {{ member.full_name }} </div>
|
||||
<div style="margin-top: 0.5rem;"> {{ _("has successfully completed the course on") }}
|
||||
<b> {{ course.title }} </b> on {{ frappe.utils.format_date(certificate.issue_date, "medium") }}. </div>
|
||||
|
||||
<div style="font-size: 2rem; font-weight: 500; color: #192734;">
|
||||
{{ member.full_name }}
|
||||
</div>
|
||||
<div style="margin-top: 0.5rem;">
|
||||
{{ _("has successfully completed the course on") }}
|
||||
<b> {{ course.title }} </b>
|
||||
on {{ frappe.utils.format_date(certificate.issue_date, "medium") }}.
|
||||
</div>
|
||||
|
||||
<div style="display: flex; justify-content: center; margin: 4rem auto 0; width: fit-content;">
|
||||
|
||||
{% if instructors %}
|
||||
<div>
|
||||
<div style="color: #192734; font-weight: bold; font-family: cursive; font-size: 1.25rem;">
|
||||
{{ instructors }}
|
||||
</div>
|
||||
<hr style="margin: 0.5rem 0;">
|
||||
<div> {{ _("Course Instructor") }} </div>
|
||||
<div class="text-center"> {{ _("Course Instructor") }} </div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if certificate.expiry_date %}
|
||||
<div style="margin-left: 2rem;">
|
||||
<div style="color: #192734; font-weight: bold; font-family: cursive; font-size: 1.25rem;">
|
||||
{{ frappe.utils.format_date(certificate.expiry_date, "medium") }}
|
||||
</div>
|
||||
<hr style="margin: 0.5rem 0;">
|
||||
<div> {{ _("Expiry date") }} </div>
|
||||
<div style="color: #192734; font-weight: bold; font-family: cursive; font-size: 1.25rem;">
|
||||
{{ frappe.utils.format_date(certificate.expiry_date, "medium") }}
|
||||
</div>
|
||||
<hr style="margin: 0.5rem 0;">
|
||||
<div class="text-center"> {{ _("Expiry date") }} </div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
30
lms/templates/certificates_section.html
Normal file
30
lms/templates/certificates_section.html
Normal file
@@ -0,0 +1,30 @@
|
||||
{% set certificates = get_certificates(user) %}
|
||||
|
||||
{% if certificates | length %}
|
||||
<div class="cards-parent">
|
||||
{% for certificate in certificates %}
|
||||
{% set course = frappe.db.get_value("LMS Course", certificate.course, ["title", "name", "image"], as_dict=True) %}
|
||||
|
||||
<div class="common-card-style column-card">
|
||||
<div class="font-weight-bold">
|
||||
{{ course.title }}
|
||||
</div>
|
||||
<div>
|
||||
{{ _("Issued on") }} : {{ frappe.utils.format_date(certificate.issue_date, "medium") }}
|
||||
</div>
|
||||
<a class="stretched-link" href="/courses/{{ course.name }}/{{ certificate.name }}"></a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
{% set course_list_link = "<a href='/courses'>course list</a>" %}
|
||||
<div class="empty-state">
|
||||
<img class="icon icon-xl" src="/assets/lms/icons/comment.svg">
|
||||
<div class="empty-state-text">
|
||||
<div class="empty-state-heading">{{ _("No certificates") }}</div>
|
||||
<div class="course-meta">{{ _("Check out the {0} to know more about certification.").format(course_list_link) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% set courses = get_authored_courses(frappe.session.user, only_published=False) %}
|
||||
{% set courses = get_authored_courses(user or None, only_published or False) %}
|
||||
|
||||
{% if courses | length %}
|
||||
<div class="cards-parent">
|
||||
@@ -9,9 +9,7 @@
|
||||
|
||||
{% else %}
|
||||
<div class="empty-state">
|
||||
<div>
|
||||
<img class="icon icon-xl" src="/assets/lms/icons/comment.svg">
|
||||
</div>
|
||||
<img class="icon icon-xl" src="/assets/lms/icons/comment.svg">
|
||||
<div class="empty-state-text">
|
||||
<div class="empty-state-heading">{{ _("No courses created") }}</div>
|
||||
<div class="course-meta">{{ _("Help others learn something new.") }}</div>
|
||||
|
||||
18
lms/templates/courses_under_review.html
Normal file
18
lms/templates/courses_under_review.html
Normal file
@@ -0,0 +1,18 @@
|
||||
{% set courses = get_courses_under_review() %}
|
||||
|
||||
{% if courses | length %}
|
||||
<div class="cards-parent">
|
||||
{% for course in courses %}
|
||||
{{ widgets.CourseCard(course=course) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<div class="empty-state">
|
||||
<img class="icon icon-xl" src="/assets/lms/icons/comment.svg">
|
||||
<div class="empty-state-text">
|
||||
<div class="empty-state-heading">{{ _("No courses under review") }}</div>
|
||||
<div class="course-meta">{{ _("When a course gets submitted for review, it will be listed here.") }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -48,25 +48,25 @@
|
||||
<script>
|
||||
frappe.ready(() => {
|
||||
$("#confirm").click((e) => {
|
||||
frappe.call({
|
||||
"method": "lms.lms.doctype.lms_batch_membership.lms_batch_membership.create_membership",
|
||||
"args": {
|
||||
"batch": {{ batch.name }},
|
||||
"course": {{ batch.course }}
|
||||
},
|
||||
"callback": (data) => {
|
||||
if (data.message == "OK") {
|
||||
frappe.msgprint({
|
||||
message: __("You are now a member of this batch!"),
|
||||
clear: true
|
||||
});
|
||||
setTimeout(function () {
|
||||
window.location.href = "/courses/{{ batch.course }}/home";
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
frappe.call({
|
||||
"method": "lms.lms.doctype.lms_batch_membership.lms_batch_membership.create_membership",
|
||||
"args": {
|
||||
"batch": {{ batch.name }},
|
||||
"course": {{ batch.course }}
|
||||
},
|
||||
"callback": (data) => {
|
||||
if (data.message == "OK") {
|
||||
frappe.msgprint({
|
||||
message: __("You are now a member of this batch!"),
|
||||
clear: true
|
||||
});
|
||||
setTimeout(function () {
|
||||
window.location.href = "/courses/{{ batch.course }}/home";
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -33,7 +33,9 @@
|
||||
</div>
|
||||
<div class="lesson-pagination-parent">
|
||||
{{ LessonContent(lesson) }}
|
||||
{% if not lesson.edit_mode %} {{ Discussions() }} {% endif %}
|
||||
{% if not lesson.edit_mode and course.status == "Approved" and not course.upcoming %}
|
||||
{{ Discussions() }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -57,6 +59,7 @@
|
||||
{% macro LessonContent(lesson) %}
|
||||
{% set instructors = get_instructors(course.name) %}
|
||||
{% set is_instructor = is_instructor(course.name) %}
|
||||
|
||||
<div class="common-card-style lesson-content">
|
||||
<div class="lesson-title">
|
||||
|
||||
@@ -67,10 +70,10 @@
|
||||
data-index="{{ lesson_index }}" data-course="{{ course.name }}" data-chapter="{{ chapter }}"
|
||||
{% if lesson.name %} data-lesson="{{ lesson.name }}" {% endif %}
|
||||
>{% if lesson.title %}{{ lesson.title }}{% endif %}</div>
|
||||
<span class="lesson-progress {{ hide if get_progress(course.name, lesson.name) != 'Complete' else ''}}">{{ _("COMPLETED") }}</span>
|
||||
<span class="indicator-pill green {{ hide if get_progress(course.name, lesson.name) != 'Complete' else ''}}">{{ _("COMPLETED") }}</span>
|
||||
|
||||
<!-- Edit Button -->
|
||||
{% if is_instructor and not lesson.edit_mode %}
|
||||
{% if (is_instructor or has_course_moderator_role()) and not lesson.edit_mode %}
|
||||
<button class="button is-default button-links ml-auto btn-edit"> {{ _("Edit") }} </button>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -90,12 +93,14 @@
|
||||
{% endfor %}
|
||||
<a class="button-links ml-1" href="{{ get_profile_url(instructors[0].username) }}">
|
||||
<span class="course-meta">
|
||||
{% if ins_len == 1 %}
|
||||
{{ instructors[0].full_name }}
|
||||
{% else %}
|
||||
{% set suffix = _("other") if ins_len - 1 == 1 else _("others") %}
|
||||
{{ instructors[0].full_name.split(" ")[0] }} and {{ ins_len - 1 }} {{ suffix }}
|
||||
{% endif %}
|
||||
{% if ins_len == 1 %}
|
||||
{{ instructors[0].full_name }}
|
||||
{% elif ins_len == 2 %}
|
||||
{{ instructors[0].full_name.split(" ")[0] }} and {{ instructors[1].full_name.split(" ")[0] }}
|
||||
{% else %}
|
||||
{% set suffix = "other" if ins_len - 1 == 1 else "others" %}
|
||||
{{ instructors[0].full_name.split(" ")[0] }} and {{ ins_len - 1 }} {{ suffix }}
|
||||
{% endif %}
|
||||
</span>
|
||||
</a>
|
||||
<div class="ml-5 course-meta"> {{ frappe.utils.format_date(lesson.creation, "medium") }} </div>
|
||||
@@ -103,7 +108,7 @@
|
||||
|
||||
<!-- Lesson Content -->
|
||||
<div class="markdown-source lesson-content-card {% if lesson.edit_mode %} mb-0 mt-2 {% endif %} ">
|
||||
{% if membership or lesson.include_in_preview or is_instructor %}
|
||||
{% if show_lesson %}
|
||||
|
||||
{% if is_instructor and not lesson.include_in_preview and not lesson.edit_mode %}
|
||||
<div class="small alert alert-secondary alert-dismissible mb-4">
|
||||
@@ -115,13 +120,17 @@
|
||||
{% if lesson.edit_mode %}
|
||||
{{ EditLesson(lesson) }}
|
||||
{% else %}
|
||||
{{ render_html(lesson.body) }}
|
||||
{{ render_html(lesson.body, lesson.youtube, lesson.quiz_id) }}
|
||||
{% endif %}
|
||||
|
||||
{% else %}
|
||||
{% set course_link = "<a class='join-batch' data-course=" + course.name | urlencode + " href=''>" + _('here') + "</a>" %}
|
||||
<div class="">
|
||||
<div class="btn btn-primary pull-right join-batch" data-course="{{ course.name | urlencode }}"> {{ _("Start Learning") }} </div>
|
||||
<div class=""> {{ _("This lesson is not available for preview. Please join the course to access it.") }} </div>
|
||||
<div>
|
||||
{{ _("There is no preview available for this lesson.
|
||||
Please join the course to access it.
|
||||
Click {0} to enroll.").format(course_link) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -140,14 +149,14 @@
|
||||
<div>
|
||||
{% if prev_url %}
|
||||
<a class="btn btn-secondary dark-links prev" href="{{ prev_url }}">
|
||||
<img class="mr-2" src="/assets/lms/icons/left-arrow.svg">
|
||||
{{ _("Prev") }}
|
||||
{{ _("Previous") }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if not is_mentor(course.name, frappe.session.user) and membership %}
|
||||
{% set progress = get_progress(course.name, lesson.name) %}
|
||||
|
||||
{% if not is_mentor(course.name, frappe.session.user) and membership %}
|
||||
<div class="custom-checkbox {% if progress == 'Complete' %} hide {% endif %}">
|
||||
<label class="quiz-label">
|
||||
<input class="mark-progress" type="checkbox" checked>
|
||||
@@ -155,18 +164,19 @@
|
||||
<span class="small">{{ _("Mark as complete on moving to the next lesson") }}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="btn btn-default mark-progress {{ progress }} {% if progress == 'Incomplete' or progress == None %} hide {% endif %}"
|
||||
data-progress="Incomplete">
|
||||
{{ _("Mark as Incomplete") }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div>
|
||||
<a class="btn btn-primary next {% if not next_url and (membership.progress|int == 100 or is_instructor) %} hide {% endif %}"
|
||||
{% if next_url %} data-href="{{ next_url }}" {% endif %} href="">
|
||||
{% if next_url %} {{ _("Next") }} {% else %} {{ _("Mark as Complete") }} {% endif %}
|
||||
<img class="ml-2" src="/assets/lms/icons/side-arrow-white.svg">
|
||||
{% if not is_mentor(course.name, frappe.session.user) and membership %}
|
||||
<div class="btn btn-default mark-progress {{ progress }} {% if progress == 'Incomplete' or progress == None %} hide {% endif %}"
|
||||
data-progress="Incomplete">
|
||||
{{ _("Mark as Incomplete") }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<a class="btn btn-primary next ml-2 {% if not next_url and (membership.progress|int == 100 or is_instructor) %} hide {% endif %}"
|
||||
{% if next_url %} data-href="{{ next_url }}" {% endif %} href="">
|
||||
{% if next_url %} {{ _("Next") }} {% else %} {{ _("Mark as Complete") }} {% endif %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -176,20 +186,34 @@
|
||||
|
||||
<!-- Edit Lesson -->
|
||||
{% macro EditLesson(lesson) %}
|
||||
<div id="body" {% if lesson.body %} data-body="{{ lesson.body }}" {% endif %}></div>
|
||||
|
||||
<label class="preview">
|
||||
<input {% if lesson.include_in_preview %} checked {% endif %} type="checkbox"
|
||||
id="preview"> {{ _("Show preview of this lesson to Guest users.") }}
|
||||
</label>
|
||||
<div class="medium mt-2" contenteditable="true" data-placeholder="{{ _('YouTube Video ID') }}"
|
||||
id="youtube">{% if lesson.youtube %}{{ lesson.youtube }}{% endif %}</div>
|
||||
<div id="body" {% if lesson.body %} data-body="{{ lesson.body }}" {% endif %}></div>
|
||||
<div class="medium mb-4" contenteditable="true" data-placeholder="{{ _('Quiz ID') }}"
|
||||
id="quiz-id">{% if lesson.quiz_id %}{{ lesson.quiz_id }}{% endif %}</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<button class="btn btn-primary btn-sm btn-lesson pull-right ml-2"> {{ _("Save") }} </button>
|
||||
{% if lesson.name %}
|
||||
<button class="btn btn-secondary btn-sm pull-right btn-back ml-2"> {{ _("Back to Lesson") }} </button>
|
||||
<a class="btn btn-secondary btn-sm pull-right" href="/quizzes"> {{ _("Create Quiz") }} </a>
|
||||
{% endif %}
|
||||
<label class="preview" for="preview">
|
||||
<input {% if lesson.include_in_preview %} checked {% endif %} type="checkbox" id="preview">
|
||||
{{ _("Show preview of this lesson to Guest users.") }}
|
||||
</label>
|
||||
|
||||
<div class="mt-4">
|
||||
<button class="btn btn-primary btn-sm btn-lesson pull-right ml-2"> {{ _("Save") }} </button>
|
||||
{% if lesson.name %}
|
||||
<button class="btn btn-secondary btn-sm pull-right btn-back ml-2"> {{ _("Back to Lesson") }} </button>
|
||||
<a class="btn btn-secondary btn-sm pull-right" href="/quizzes"> {{ _("Create Quiz") }} </a>
|
||||
{% endif %}
|
||||
|
||||
{{ UploadAttachments() }}
|
||||
|
||||
</div>
|
||||
|
||||
{{ HelpArticle() }}
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro UploadAttachments() %}
|
||||
<div class="attachments-parent">
|
||||
<div class="attachment-controls">
|
||||
<div class="show-attachments" data-toggle="collapse" data-target="#collapse-attachments" aria-expanded="false">
|
||||
@@ -209,94 +233,111 @@
|
||||
</div>
|
||||
<table class="attachments common-card-style collapse hide" id="collapse-attachments"></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ HelpArticle() }}
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
<!-- Help Article -->
|
||||
{% macro HelpArticle() %}
|
||||
<div class="help-article">
|
||||
<h3> {{ _("Help Article") }} </h3>
|
||||
<p>
|
||||
{{ _("You can add additional content to the lesson using a special syntax. The table below mentions
|
||||
all types of dynamic content that you can add to the lessons and the syntax for the same.") }}
|
||||
</p>
|
||||
<table class="table w-100">
|
||||
<tr>
|
||||
<th style="width: 20%;"> {{ _("Content Type") }} </th>
|
||||
<th style="width: 40%;"> {{ _("Syntax") }} </th>
|
||||
<th> {{ _("Description") }} </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
{{ _("YouTube Video") }}
|
||||
</td>
|
||||
<td>
|
||||
{% raw %} {{ YouTubeVideo("embed_src") }} {% endraw %}
|
||||
</td>
|
||||
<td>
|
||||
<span>
|
||||
{{ _("Copy and paste the syntax in the editor. Replace 'embed_src' with the embed source
|
||||
that YouTube provides. To get the source, follow the steps mentioned below.") }}
|
||||
</span>
|
||||
<ul class="p-4">
|
||||
<div class="medium">
|
||||
<h3> {{ _("Embed Components") }} </h3>
|
||||
<p>
|
||||
{{ _("You can add additional content to the lesson using a special syntax. The table below mentions
|
||||
all types of dynamic content that you can add to the lessons and the syntax for the same.") }}
|
||||
</p>
|
||||
<ol>
|
||||
<li>
|
||||
<b> {{ _("YouTube Video") }} </b>
|
||||
<p> To get the YouTube Video ID, follow the steps mentioned below. </p>
|
||||
<ul class="px-4">
|
||||
<li>
|
||||
{{ _("Upload the video on youtube.") }}
|
||||
</li>
|
||||
<li>
|
||||
{{ _("When you share a youtube video, it shows an option called Embed.") }}
|
||||
{{ _("When you share a youtube video, it shows a URL") }}
|
||||
</li>
|
||||
<li>
|
||||
{{ _("On clicking it, it provides an iframe. Copy the source (src) of the iframe and
|
||||
paste it here.") }}
|
||||
{{ _("Copy the last parameter of the URL and paste it here.") }}
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
{{ _("Quiz") }}
|
||||
</td>
|
||||
<td>
|
||||
{% raw %} {{ Quiz("lms_quiz_id") }} {% endraw %}
|
||||
</td>
|
||||
<td>
|
||||
{% set quiz_link = "<a href='/quizzes'> Quiz List </a>" %}
|
||||
{{ _("Copy and paste the syntax in the editor. Replace 'lms_quiz_id' with the ID of the Quiz. You can get the ID of the quiz from the {0}.").format(quiz_link) }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
<!-- <table class="table w-100">
|
||||
<tr>
|
||||
<th style="width: 20%;"> {{ _("Content Type") }} </th>
|
||||
<th style="width: 40%;"> {{ _("Syntax") }} </th>
|
||||
<th> {{ _("Description") }} </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
{{ _("YouTube Video") }}
|
||||
</td>
|
||||
<td>
|
||||
{% raw %} {{ YouTubeVideo('Video ID') }} {% endraw %}
|
||||
</td>
|
||||
<td>
|
||||
<span>
|
||||
{{ _("Copy and paste the syntax in the editor. Replace 'Video ID' with the embed source
|
||||
that YouTube provides. To get the source, follow the steps mentioned below.") }}
|
||||
</span>
|
||||
<ul class="p-4">
|
||||
<li>
|
||||
{{ _("Upload the video on youtube.") }}
|
||||
</li>
|
||||
<li>
|
||||
{{ _("When you share a youtube video, it shows an option called Embed.") }}
|
||||
</li>
|
||||
<li>
|
||||
{{ _("On clicking it, it provides an iframe. Copy the source (src) of the iframe and
|
||||
paste it here.") }}
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
{{ _("Quiz") }}
|
||||
</td>
|
||||
<td>
|
||||
{% raw %} {{ Quiz('Quiz ID') }} {% endraw %}
|
||||
</td>
|
||||
<td>
|
||||
{% set quiz_link = "<a href='/quizzes'> Quiz List </a>" %}
|
||||
{{ _("Copy and paste the syntax in the editor. Replace 'Quiz ID' with the Id of the Quiz.
|
||||
You can get the Id of the quiz from the {0}.").format(quiz_link) }}
|
||||
</td>
|
||||
</tr>
|
||||
</table> -->
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
<!-- Discussions Component -->
|
||||
{% macro Discussions() %}
|
||||
{% set topics_count = frappe.db.count("Discussion Topic",
|
||||
{"reference_doctype": "Course Lesson", "reference_docname": lesson.name}) %}
|
||||
{% set is_instructor = frappe.session.user == course.instructor %}
|
||||
{% set condition = is_instructor if is_instructor else membership %}
|
||||
{% set doctype, docname = _("Course Lesson"), lesson.name %}
|
||||
{% set title = "Questions" if topics_count else "" %}
|
||||
{% set cta_title = "Ask a Question" %}
|
||||
{% set button_name = _("Start Learning") %}
|
||||
{% set redirect_to = "/courses/" + course.name %}
|
||||
{% set empty_state_title = _("Have a doubt?") %}
|
||||
{% set empty_state_subtitle = _("Post it here, our mentors will help you out.") %}
|
||||
{% include "frappe/templates/discussions/discussions_section.html" %}
|
||||
{% set topics_count = frappe.db.count("Discussion Topic", {
|
||||
"reference_doctype": "Course Lesson",
|
||||
"reference_docname": lesson.name
|
||||
}) %}
|
||||
{% set is_instructor = frappe.session.user == course.instructor %}
|
||||
{% set condition = is_instructor if is_instructor else membership %}
|
||||
{% set doctype, docname = _("Course Lesson"), lesson.name %}
|
||||
{% set title = "Questions" if topics_count else "" %}
|
||||
{% set cta_title = "Ask a Question" %}
|
||||
{% set button_name = _("Start Learning") %}
|
||||
{% set redirect_to = "/courses/" + course.name %}
|
||||
{% set empty_state_title = _("Have a doubt?") %}
|
||||
{% set empty_state_subtitle = _("Post it here, our mentors will help you out.") %}
|
||||
{% include "frappe/templates/discussions/discussions_section.html" %}
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
<!-- Scripts -->
|
||||
{%- block script %}
|
||||
{{ super() }}
|
||||
<script type="text/javascript">
|
||||
var page_context = {{ page_context | tojson }};
|
||||
</script>
|
||||
{{ include_script('controls.bundle.js') }}
|
||||
{% for ext in page_extensions %}
|
||||
{{ ext.render_footer() }}
|
||||
{% endfor %}
|
||||
{{ super() }}
|
||||
<script type="text/javascript">
|
||||
var page_context = {{ page_context | tojson }};
|
||||
</script>
|
||||
{{ include_script('controls.bundle.js') }}
|
||||
{% for ext in page_extensions %}
|
||||
{{ ext.render_footer() }}
|
||||
{% endfor %}
|
||||
{%- endblock %}
|
||||
|
||||
@@ -66,7 +66,7 @@ frappe.ready(() => {
|
||||
|
||||
$(".btn-back").click((e) => {
|
||||
window.location.href = window.location.href.split("?")[0];
|
||||
})
|
||||
});
|
||||
|
||||
$(document).on("click", ".copy-link", (e) => {
|
||||
frappe.utils.copy_to_clipboard($(e.currentTarget).data("link"));
|
||||
@@ -490,14 +490,22 @@ const save_lesson = (e) => {
|
||||
method: "lms.lms.doctype.lms_course.lms_course.save_lesson",
|
||||
args: {
|
||||
"title": $("#title").text(),
|
||||
"body": this.code_field_group.fields_dict["code_md"].last_value,
|
||||
"body": this.code_field_group.fields_dict["code_md"].value,
|
||||
"youtube": $("#youtube").text(),
|
||||
"quiz_id": $("#quiz-id").text(),
|
||||
"chapter": $("#title").data("chapter"),
|
||||
"preview": $("#preview").prop("checked") ? 1 : 0,
|
||||
"idx": $("#title").data("index"),
|
||||
"lesson": lesson ? lesson : ""
|
||||
},
|
||||
callback: (data) => {
|
||||
window.location.href = window.location.href.split("?")[0];
|
||||
callback: (data) => {;
|
||||
frappe.show_alert({
|
||||
message: __("Saved"),
|
||||
indicator: "green",
|
||||
});
|
||||
setTimeout(() => {
|
||||
window.location.href = window.location.href.split("?")[0];
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import frappe
|
||||
from lms.www.utils import get_common_context, redirect_to_lesson
|
||||
from lms.lms.utils import get_lesson_url, is_instructor, redirect_to_courses_list
|
||||
from lms.lms.utils import get_lesson_url, has_course_moderator_role, is_instructor, redirect_to_courses_list
|
||||
from frappe.utils import cstr, flt
|
||||
|
||||
def get_context(context):
|
||||
@@ -23,11 +23,14 @@ def get_context(context):
|
||||
redirect_to_lesson(context.course, index_)
|
||||
|
||||
context.lesson = get_current_lesson_details(lesson_number, context)
|
||||
instructor = is_instructor(context.course.name)
|
||||
context.show_lesson = context.membership or (context.lesson and context.lesson.include_in_preview) or instructor or has_course_moderator_role()
|
||||
|
||||
if not context.lesson:
|
||||
context.lesson = frappe._dict()
|
||||
|
||||
if frappe.form_dict.get("edit"):
|
||||
if not is_instructor(context.course.name):
|
||||
if not instructor and not has_course_moderator_role():
|
||||
redirect_to_courses_list()
|
||||
context.lesson.edit_mode = True
|
||||
else:
|
||||
|
||||
@@ -52,11 +52,13 @@
|
||||
<div class="d-flex justify-content-between option-{{ num }}">
|
||||
<div contenteditable="true" data-placeholder="{{ _('Option') }}"
|
||||
class="option-input">{% if option %}{{ option }}{% endif %}</div>
|
||||
<div contenteditable="true" data-placeholder="{{ _('Explanation') }}"
|
||||
<div contenteditable="true" data-placeholder="{{ _('Explain the option') }}"
|
||||
class="option-input">{% if explanation %}{{ explanation }}{% endif %}</div>
|
||||
<div class="option-checkbox">
|
||||
<input type="checkbox" {% if question['is_correct_' + num] %} checked {% endif %}>
|
||||
<label class="mb-0"> {{ _("Is Correct") }} </label>
|
||||
<label class="mb-0">
|
||||
<input type="checkbox" {% if question['is_correct_' + num] %} checked {% endif %}>
|
||||
{{ _("Is Correct") }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -68,8 +70,17 @@
|
||||
|
||||
<div class="mt-4">
|
||||
<button class="btn btn-secondary btn-sm btn-question"> {{ _("New Question") }} </button>
|
||||
<button class="btn btn-primary btn-sm btn-save-question ml-2
|
||||
{% if not quiz.name %} hide {% endif %}"> {{ _("Save Quiz") }} </button>
|
||||
|
||||
{% if quiz.name %}
|
||||
<button class="btn btn-secondary btn-sm copy-quiz-id ml-2" data-name="'{{ quiz.name }}'">
|
||||
{{ _("Copy Quiz ID") }}
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
<button class="btn btn-primary btn-sm btn-save-question ml-2 {% if not quiz.name %} hide {% endif %}">
|
||||
{{ _("Save Quiz") }}
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
@@ -1,21 +1,34 @@
|
||||
frappe.ready(() => {
|
||||
|
||||
if(!$(".quiz-card").length) {
|
||||
add_question();
|
||||
}
|
||||
|
||||
$(".btn-question").click((e) => {
|
||||
add_question(e);
|
||||
add_question();
|
||||
});
|
||||
|
||||
$(".btn-save-question").click((e) => {
|
||||
save_question(e);
|
||||
});
|
||||
|
||||
get_questions()
|
||||
$(".copy-quiz-id").click((e) => {
|
||||
frappe.utils.copy_to_clipboard($(e.currentTarget).data("name"));
|
||||
});
|
||||
|
||||
get_questions();
|
||||
|
||||
});
|
||||
|
||||
|
||||
const add_question = (e) => {
|
||||
const add_question = () => {
|
||||
if ($(".new-quiz-card").length) {
|
||||
scroll_to_question_container();
|
||||
return;
|
||||
}
|
||||
|
||||
let add_after = $(".quiz-card").length ? $(".quiz-card:last") : $("#quiz-title");
|
||||
let question_template = `<div class="quiz-card">
|
||||
let question_template = `<div class="quiz-card new-quiz-card">
|
||||
<div contenteditable="true" data-placeholder="${__("Question")}" class="question mb-4"></div>
|
||||
</div>`;
|
||||
$(question_template).insertAfter(add_after);
|
||||
@@ -54,6 +67,7 @@ const save_question = (e) => {
|
||||
if (!$("#quiz-title").text()) {
|
||||
frappe.throw(__("Quiz Title is mandatory."));
|
||||
}
|
||||
|
||||
frappe.call({
|
||||
method: "lms.lms.doctype.lms_quiz.lms_quiz.save_quiz",
|
||||
args: {
|
||||
@@ -104,3 +118,11 @@ const get_questions = () => {
|
||||
|
||||
return questions;
|
||||
};
|
||||
|
||||
|
||||
const scroll_to_question_container = () => {
|
||||
$([document.documentElement, document.body]).animate({
|
||||
scrollTop: $(".new-quiz-card").offset().top
|
||||
}, 1000);
|
||||
$(".new-quiz-card").find(".question").focus();
|
||||
}
|
||||
|
||||
@@ -12,37 +12,55 @@
|
||||
{% block content %}
|
||||
<div class="common-page-style">
|
||||
<div class="container">
|
||||
|
||||
<a class="btn btn-secondary btn-sm pull-right" href="/quizzes/new-quiz">
|
||||
{{ _("Add Quiz") }}
|
||||
</a>
|
||||
<div class="course-home-headings">
|
||||
{{ _("Quiz List") }}
|
||||
</div>
|
||||
|
||||
{% if quiz_list | length %}
|
||||
<a class="btn btn-secondary btn-sm pull-right" href="/quizzes/new-quiz"> {{ _("Add Quiz") }} </a>
|
||||
<div class="course-home-headings"> {{ _("Quiz List") }} </div>
|
||||
<div class="common-card-style">
|
||||
<table class="table">
|
||||
|
||||
<tr style="background-color: var(--fg-hover-color); font-weight: bold">
|
||||
<td style="width: 10%;"> {{ _("No.") }} </td>
|
||||
<td style="width: 45%;"> {{ _("Title") }} </td>
|
||||
<td> {{ _("ID") }} </td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
|
||||
{% for quiz in quiz_list %}
|
||||
<tr style="position: relative; color: var(--text-color);">
|
||||
<tr class="quiz-row" data-name="{{ quiz.name }}">
|
||||
<td> {{ loop.index }} </td>
|
||||
<td>
|
||||
<a class="button-links" href="/quizzes/{{ quiz.name }}">{{ quiz.title }}</a>
|
||||
{{ quiz.title }}
|
||||
</td>
|
||||
<td>
|
||||
<a class="button-links" href="/quizzes/{{ quiz.name }}">{{ quiz.name }}</a>
|
||||
{{ quiz.name }}
|
||||
</td>
|
||||
<td>
|
||||
<a class="btn btn-secondary btn-sm copy-quiz-id" data-name="'{{ quiz.name }}'">
|
||||
{{ _("Copy Quiz ID") }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<div class="empty-state">
|
||||
<img class="icon icon-xl" src="/assets/lms/icons/comment.svg">
|
||||
<div class="empty-state-text">
|
||||
<div class="empty-state-heading">{{ _("You have not created any quiz yet.") }}</div>
|
||||
<div class="course-meta mb-6">{{ _("Create a quiz and add it to your course to engage users.") }}</div>
|
||||
<a class="btn btn-secondary btn-sm"
|
||||
href="{% if frappe.session.user == 'Guest' %} /login?redirect-to=/quizzes {% else %} /quizzes/new-quiz {% endif %}">
|
||||
{{ _("Add Quiz") }} </a>
|
||||
<div class="empty-state-heading">
|
||||
{{ _("You have not created any quiz yet.") }}
|
||||
</div>
|
||||
<div class="course-meta ">
|
||||
{{ _("Create a quiz and add it to your course to engage your users.") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -50,3 +68,22 @@
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block script %}
|
||||
<script>
|
||||
frappe.ready(() => {
|
||||
|
||||
$(".copy-quiz-id").click((e) => {
|
||||
e.preventDefault();
|
||||
frappe.utils.copy_to_clipboard($(e.currentTarget).data("name"));
|
||||
});
|
||||
|
||||
$(".quiz-row").click((e) => {
|
||||
if (!$(e.target).hasClass("copy-quiz-id")) {
|
||||
window.location.href = `/quizzes/${$(e.currentTarget).data('name')}`;
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<div class="common-page-style">
|
||||
<div class="container">
|
||||
|
||||
<input class="search" id="search-user" placeholder="{{ _('Try a Name, Company, or Industry') }}">
|
||||
<input class="search pull-right" id="search-user" placeholder="{{ _('Search') }}">
|
||||
|
||||
<div class="course-home-headings">{{ _("Community") }} </div>
|
||||
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
<a class="dark-links" href="/courses/{{ course.name }}">{{ course.title }}</a>
|
||||
</div>
|
||||
{% if custom_template %}
|
||||
{{ custom_template }}
|
||||
{{ custom_template }}
|
||||
{% else %}
|
||||
{% include "lms/templates/certificate.html" %}
|
||||
{% include "lms/templates/certificate.html" %}
|
||||
{% endif %}
|
||||
<script src="/assets/lms/js/html2canvas.js"></script>
|
||||
|
||||
|
||||
@@ -19,8 +19,7 @@ def get_context(context):
|
||||
|
||||
context.course = frappe.db.get_value("LMS Course", course_name, ["title", "name", "image"], as_dict=True)
|
||||
context.instructors = (", ").join([x.full_name for x in get_instructors(course_name)])
|
||||
context.member = frappe.db.get_value("User", context.certificate.member,
|
||||
["full_name"], as_dict=True)
|
||||
context.member = frappe.db.get_value("User", context.certificate.member, ["full_name"], as_dict=True)
|
||||
|
||||
context.logo = frappe.db.get_single_value("Website Settings", "banner_image")
|
||||
template_name = frappe.db.get_single_value("LMS Settings", "custom_certificate_template")
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
{% block head_include %}
|
||||
{% include "public/icons/symbol-defs.svg" %}
|
||||
<link rel="stylesheet" href="/assets/frappe/css/font-awesome.css">
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -18,10 +17,11 @@
|
||||
<div class="container">
|
||||
<div class="course-body-container">
|
||||
{{ CourseHeaderOverlay(course) }}
|
||||
{{ CourseSettings(course) }}
|
||||
{{ Description(course) }}
|
||||
{{ Save(course) }}
|
||||
{{ widgets.CourseOutline(course=course, membership=membership, is_user_interested=is_user_interested) }}
|
||||
{% if not course.edit_mode %}
|
||||
{% if not course.edit_mode and course.status == "Approved" and not frappe.utils.cint(course.upcoming) %}
|
||||
{{ widgets.Reviews(course=course, membership=membership) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -114,7 +114,9 @@
|
||||
</div>
|
||||
|
||||
<div class="course-image-attachment {% if not course.image %} hide {% endif %} ">
|
||||
<a href="{{ course.image }}" id="image" target="_blank"> {{ course.image }} </a>
|
||||
<a {% if course.image %} href="{{ course.image }}" {% endif %} id="image" target="_blank">
|
||||
{{ course.image }}
|
||||
</a>
|
||||
<button class="btn btn-sm btn-default btn-clear ml-4"> {{ _("Clear") }} </button>
|
||||
</div>
|
||||
<a class="btn btn-default btn-sm btn-attach mt-1 {% if course.image %} hide {% endif %}"> {{ _("Attach Image") }} </a>
|
||||
@@ -202,9 +204,32 @@
|
||||
|
||||
<!-- Description -->
|
||||
{% macro Description(course) %}
|
||||
<div class="course-description-section" {% if course.edit_mode %} style="min-height: 100px" {% endif %}
|
||||
{% if course.edit_mode %} contenteditable="true" {% endif %} id="description"
|
||||
data-placeholder="Description">{% if course.description %}{{ frappe.utils.md_to_html(course.description) }}{% endif %}</div>
|
||||
{% if course.edit_mode %}
|
||||
<div id="description" {% if course.description %} data-description="{{ course.description }}" {% endif %}></div>
|
||||
{% else %}
|
||||
<div class="course-description-section">
|
||||
{{ frappe.utils.md_to_html(course.description) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
<!-- Course Settings -->
|
||||
{% macro CourseSettings(course) %}
|
||||
|
||||
{% if course.edit_mode and has_course_moderator_role() %}
|
||||
<div class="mb-4">
|
||||
<label for="published" class="mb-0">
|
||||
<input type="checkbox" id="published" {% if course.published %} checked {% endif %}>
|
||||
{{ _("Published") }}
|
||||
</label>
|
||||
<label for="upcoming" class="mb-0 ml-20">
|
||||
<input type="checkbox" id="upcoming" {% if course.upcoming %} checked {% endif %}>
|
||||
{{ _("Upcoming") }}
|
||||
</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
@@ -225,23 +250,6 @@
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro CourseCreator(course) %}
|
||||
<div class="course-home-headings"> {{ _("Course Creators") }} </div>
|
||||
<div class="common-card-style course-creators-card">
|
||||
{% set instructors = get_instructors(course.name) %}
|
||||
{% for instructor in instructors %}
|
||||
<div class="d-flex align-items-center">
|
||||
{{ widgets.Avatar(member=instructor, avatar_class="avatar-medium") }}
|
||||
<div class="ml-4">
|
||||
<div class="course-creator-name"> {{ instructor.full_name }} </div>
|
||||
<div class="course-meta"> {{ get_authored_courses(instructor.name) | length }} {{ _("Courses Created") }} </div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
<!-- Related Courses Section -->
|
||||
{% macro RelatedCourses(course) %}
|
||||
{% if course.related_courses | length %}
|
||||
@@ -285,7 +293,7 @@
|
||||
membership.current_lesson else "1.1" if first_lesson_exists(course.name) else None %}
|
||||
|
||||
{% if show_start_learing_cta %}
|
||||
<div class="btn btn-primary wide-button join-batch" data-course="{{ course.name | urlencode }}">
|
||||
<div class="btn btn-primary wide-button join-batch mb-2" data-course="{{ course.name | urlencode }}">
|
||||
{{ _("Start Learning") }}
|
||||
<img class="ml-2" src="/assets/lms/icons/white-arrow.svg" />
|
||||
</div>
|
||||
@@ -301,7 +309,7 @@
|
||||
{{ _("Checkout Course") }} <img class="ml-2" src="/assets/lms/icons/white-arrow.svg" />
|
||||
</a>
|
||||
|
||||
{% elif course.upcoming and not is_user_interested %}
|
||||
{% elif course.upcoming and not is_user_interested and not is_instructor %}
|
||||
<div class="btn btn-secondary wide-button notify-me" data-course="{{course.name | urlencode}}">
|
||||
{{ _("Notify me when available") }}
|
||||
</div>
|
||||
@@ -338,7 +346,7 @@
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if is_instructor(course.name) %}
|
||||
{% if is_instructor(course.name) or has_course_moderator_role() %}
|
||||
<a class="btn btn-secondary wide-button" href="/courses/{{ course.name }}?edit=1"> {{ _("Edit Course") }} </a>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
@@ -356,9 +364,9 @@
|
||||
frappe.utils.format_time(certificate_request.start_time, "short")) }} </p>
|
||||
{% endif %}
|
||||
|
||||
{% if course.status == "Under Review" %}
|
||||
{% if course.status == "Under Review" and is_instructor(course.name) %}
|
||||
<div class="mb-4">
|
||||
{{ _("Your course is currently under review. Once the review is complete, the System Admins will publish it on the website.") }}
|
||||
{{ _("This course is currently under review. Once the review is complete, the System Admins will publish it on the website.") }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
@@ -70,6 +70,10 @@ frappe.ready(() => {
|
||||
remove_tag(e);
|
||||
});
|
||||
|
||||
if ($("#description").length) {
|
||||
make_editor();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -206,7 +210,7 @@ const submit_for_review = (e) => {
|
||||
}, 3);
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 3000);
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -329,6 +333,7 @@ const add_tag = (e) => {
|
||||
const save_course = (e) => {
|
||||
let tags = $('.course-card-pills').map((i, el) => $(el).text().trim()).get();
|
||||
tags = tags.filter(word => word.trim().length > 0);
|
||||
|
||||
frappe.call({
|
||||
method: "lms.lms.doctype.lms_course.lms_course.save_course",
|
||||
args: {
|
||||
@@ -337,8 +342,10 @@ const save_course = (e) => {
|
||||
"short_introduction": $("#intro").text(),
|
||||
"video_link": $("#video-link").text(),
|
||||
"image": $("#image").attr("href"),
|
||||
"description": $("#description").text(),
|
||||
"course": $("#title").data("course") ? $("#title").data("course") : ""
|
||||
"description": this.code_field_group.fields_dict["code_md"].value,
|
||||
"course": $("#title").data("course") ? $("#title").data("course") : "",
|
||||
"published": $("#published").prop("checked") ? 1 : 0,
|
||||
"upcoming": $("#upcoming").prop("checked") ? 1 : 0
|
||||
},
|
||||
callback: (data) => {
|
||||
frappe.show_alert({
|
||||
@@ -356,3 +363,26 @@ const save_course = (e) => {
|
||||
const remove_tag = (e) => {
|
||||
$(e.currentTarget).closest(".course-card-pills").remove();
|
||||
};
|
||||
|
||||
|
||||
const make_editor = () => {
|
||||
this.code_field_group = new frappe.ui.FieldGroup({
|
||||
fields: [
|
||||
{
|
||||
fieldname: "code_md",
|
||||
fieldtype: "Code",
|
||||
options: "Markdown",
|
||||
wrap: true,
|
||||
max_lines: Infinity,
|
||||
min_lines: 20,
|
||||
default: $("#description").data("description"),
|
||||
depends_on: 'eval:doc.type=="Markdown"',
|
||||
}
|
||||
],
|
||||
body: $("#description").get(0),
|
||||
});
|
||||
this.code_field_group.make();
|
||||
$("#description .form-section:last").removeClass("empty-section");
|
||||
$("#description .frappe-control").removeClass("hide-control");
|
||||
$("#description .form-column").addClass("p-0");
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import frappe
|
||||
from lms.lms.doctype.lms_settings.lms_settings import check_profile_restriction
|
||||
from lms.lms.utils import get_membership, is_instructor, is_certified, get_evaluation_details, redirect_to_courses_list
|
||||
from lms.lms.utils import get_membership, has_course_moderator_role, is_instructor, is_certified, get_evaluation_details, redirect_to_courses_list
|
||||
|
||||
def get_context(context):
|
||||
context.no_cache = 1
|
||||
@@ -28,7 +28,7 @@ def set_course_context(context, course_name):
|
||||
as_dict=True)
|
||||
|
||||
if frappe.form_dict.get("edit"):
|
||||
if not is_instructor(course.name):
|
||||
if not is_instructor(course.name) and not has_course_moderator_role():
|
||||
redirect_to_courses_list()
|
||||
course.edit_mode = True
|
||||
|
||||
@@ -73,4 +73,4 @@ def get_user_interest(course):
|
||||
|
||||
|
||||
def show_start_learing_cta(course, membership, restriction):
|
||||
return not course.disable_self_learning and not membership and not course.upcoming and not restriction.get("restrict") and not is_instructor(course.name)
|
||||
return not course.disable_self_learning and not membership and not course.upcoming and not restriction.get("restrict") and not is_instructor(course.name) and course.status == "Approved"
|
||||
|
||||
@@ -6,29 +6,33 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="common-page-style">
|
||||
<div class="container">
|
||||
{% if restriction.restrict %}
|
||||
{% set profile_link = "<a href='/edit-profile'> profile </a>" %}
|
||||
<div class="empty-state">
|
||||
<div class="course-home-headings text-center mb-0" style="color: inherit;">{{ _("You haven't completed your profile.") }}</div>
|
||||
<p class="small text-center">{{ _("Complete your {0} to access the courses.").format(profile_link) }}</p>
|
||||
<div class="container">
|
||||
{% if restriction.restrict %}
|
||||
{% set profile_link = "<a href='/edit-profile'> profile </a>" %}
|
||||
<div class="empty-state">
|
||||
<div class="course-home-headings text-center mb-0" style="color: inherit;">
|
||||
{{ _("You haven't completed your profile.") }}
|
||||
</div>
|
||||
<p class="small text-center">
|
||||
{{ _("Complete your {0} to access the courses.").format(profile_link) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
{% include "lms/templates/search_course/search_course.html" %}
|
||||
|
||||
<div class="course-list">
|
||||
{% set courses = live_courses %}
|
||||
{% set title = _("Live Courses ({0})").format(courses | length) %}
|
||||
{% set classes = "live-courses" %}
|
||||
{% include "lms/templates/course_list.html" %}
|
||||
|
||||
{% set courses = upcoming_courses %}
|
||||
{% set title = _("Upcoming Courses ({0})").format(courses | length) %}
|
||||
{% set classes = "upcoming-courses mt-10" %}
|
||||
{% include "lms/templates/course_list.html" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
{% include "lms/templates/search_course/search_course.html" %}
|
||||
|
||||
<div class="course-list">
|
||||
{% set courses = live_courses %}
|
||||
{% set title = _("Live Courses ({0})").format(courses | length) %}
|
||||
{% set classes = "live-courses" %}
|
||||
{% include "lms/templates/course_list.html" %}
|
||||
|
||||
{% set courses = upcoming_courses %}
|
||||
{% set title = _("Upcoming Courses ({0})").format(courses | length) %}
|
||||
{% set classes = "upcoming-courses mt-10" %}
|
||||
{% include "lms/templates/course_list.html" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,28 +1,38 @@
|
||||
{% extends "templates/base.html" %}
|
||||
{% block title %}{{ _("Dashboard")}}
|
||||
{% block title %}
|
||||
{{ _("Dashboard")}}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
{% set portal_course_creation = frappe.db.get_single_value("LMS Settings", "portal_course_creation") %}
|
||||
{% set show_creators_section = portal_course_creation == "Anyone" or has_course_instructor_role() %}
|
||||
|
||||
<div class="common-page-style dashboard">
|
||||
<div class="container">
|
||||
|
||||
{% if show_creators_section %}
|
||||
<a class="btn btn-secondary btn-md pull-right" id="create-course-link" href="/courses/new-course">
|
||||
{{ _("Create a Course")}}
|
||||
<a class="btn btn-secondary btn-sm course-creation-link" id="create-course-link" href="/courses/new-course">
|
||||
{{ _("Create a Course") }}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<ul class="nav" id="courses-tab">
|
||||
<ul class="nav lms-nav" id="courses-tab">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" data-toggle="tab" href="#courses-enrolled"> {{ _("Courses Enrolled") }} </a>
|
||||
<a class="nav-link active" data-toggle="tab" href="#courses-enrolled">
|
||||
{{ _("Courses Enrolled") }}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{% if show_creators_section %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#courses-created">{{ _("Courses Created") }}
|
||||
<a class="nav-link" data-toggle="tab" href="#courses-created">
|
||||
{{ _("Courses Created") }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if show_review_section %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#courses-under-review">
|
||||
{{ _("Courses Under Review") }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
@@ -38,16 +48,24 @@
|
||||
<div class="tab-pane active" id="courses-enrolled" role="tabpanel" aria-labelledby="courses-enrolled">
|
||||
{% include "lms/lms/web_template/courses_enrolled/courses_enrolled.html" %}
|
||||
</div>
|
||||
|
||||
{% if show_creators_section %}
|
||||
<div class="tab-pane fade" id="courses-created" role="tabpanel" aria-labelledby="courses-created">
|
||||
{% include "lms/templates/courses_created.html" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if show_review_section %}
|
||||
<div class="tab-pane fade" id="courses-under-review" role="tabpanel" aria-labelledby="courses-under-review">
|
||||
{% include "lms/templates/courses_under_review.html" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="tab-pane fade" id="notifications" role="tabpanel" aria-labelledby="notifications">
|
||||
{% include "lms/templates/courses_created.html" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
9
lms/www/dashboard/index.py
Normal file
9
lms/www/dashboard/index.py
Normal file
@@ -0,0 +1,9 @@
|
||||
import frappe
|
||||
from lms.lms.utils import has_course_instructor_role, has_course_moderator_role
|
||||
|
||||
|
||||
def get_context(context):
|
||||
context.no_cache = 1
|
||||
portal_course_creation = frappe.db.get_single_value("LMS Settings", "portal_course_creation")
|
||||
context.show_creators_section = portal_course_creation == "Anyone" or has_course_instructor_role()
|
||||
context.show_review_section = has_course_moderator_role()
|
||||
@@ -9,7 +9,7 @@
|
||||
<div class="common-page-style">
|
||||
|
||||
<div class="container">
|
||||
{% if allow_posting and jobs | length %}
|
||||
{% if allow_posting %}
|
||||
<a class="button is-primary pull-right" href="/job-opportunity?new=1">{{ _("Post a Job") }}</a>
|
||||
{% endif %}
|
||||
|
||||
@@ -48,18 +48,11 @@
|
||||
{% else %}
|
||||
|
||||
<div class="empty-state">
|
||||
<div>
|
||||
<img class="icon icon-xl" src="/assets/lms/icons/comment.svg">
|
||||
</div>
|
||||
<img class="icon icon-xl" src="/assets/lms/icons/comment.svg">
|
||||
<div class="empty-state-text">
|
||||
<div class="empty-state-heading">{{ _("No open jobs") }}</div>
|
||||
<div class="course-meta">{{ _("There are no job openings at present.") }}</div>
|
||||
</div>
|
||||
<div>
|
||||
{% if allow_posting %}
|
||||
<a class="button is-secondary dark-links m-auto" href="/job-opportunity?new=1">{{ _("Post a Job") }}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -3,342 +3,395 @@
|
||||
<meta name="description" content="{{ member.full_name }}" />
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
{% set read_only = member.name != frappe.session.user %}
|
||||
{% set user = member.name %}
|
||||
{% set courses_created = get_authored_courses(member.name, True) %}
|
||||
{% set certificates = get_certificates(user) %}
|
||||
|
||||
<div class="common-page-style profile-page">
|
||||
{{ ProfileBanner(member) }}
|
||||
<div class="profile-page-body">
|
||||
<div class="container">
|
||||
{% set read_only = member.name != frappe.session.user %}
|
||||
{{ About(member) }}
|
||||
{{ EducationDetails(member) }}
|
||||
{{ WorkDetails(member) }}
|
||||
{{ Certification(member) }}
|
||||
{{ Contact(member) }}
|
||||
{{ Skills(member) }}
|
||||
{{ CareerPreference(member) }}
|
||||
{{ ProfileBanner(member) }}
|
||||
<div class="profile-page-body">
|
||||
<div class="container">
|
||||
|
||||
<ul class="nav lms-nav" id="courses-tab">
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" data-toggle="tab" href="#profile">
|
||||
{{ _("Profile") }}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{% if courses_created | length %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#courses-created">
|
||||
{{ _("Courses Created") }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if certificates | length %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#certificates">
|
||||
{{ _("Certificates") }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if has_course_moderator_role() %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#settings">
|
||||
{{ _("Settings") }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
</ul>
|
||||
|
||||
<div class="border-bottom mb-4"></div>
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="profile" role="tabpanel" aria-labelledby="profile">
|
||||
<div class="common-card-style column-card mt-5">
|
||||
{{ About(member) }}
|
||||
{{ EducationDetails(member) }}
|
||||
{{ WorkDetails(member) }}
|
||||
{{ ExternalCertification(member) }}
|
||||
{{ Contact(member) }}
|
||||
{{ Skills(member) }}
|
||||
{{ CareerPreference(member) }}
|
||||
{{ ProfileTabs(profile_tabs) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if courses_created | length %}
|
||||
{% set only_published = True %}
|
||||
<div class="tab-pane fade" id="courses-created" role="tabpanel" aria-labelledby="courses-created">
|
||||
{% include "lms/templates/courses_created.html" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if certificates | length %}
|
||||
<div class="tab-pane fade" id="certificates" role="tabpanel" aria-labelledby="certificates">
|
||||
{% include "lms/templates/certificates_section.html" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="tab-pane fade" id="settings" role="tabpanel" aria-labelledby="settings">
|
||||
{{ RoleSettings(member) }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
{{ CoursesCreated(member, read_only) }}
|
||||
{{ CoursesMentored(member, read_only) }}
|
||||
{{ ProfileTabs(profile_tabs) }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
<!-- Banner -->
|
||||
{% macro ProfileBanner(member) %}
|
||||
{% set cover_image = member.cover_image if member.cover_image else "/assets/lms/images/profile-banner.png" %}
|
||||
{% set enrollment = get_course_membership(frappe.session.user, member_type="Student") | length %}
|
||||
{% set enrollment = get_course_membership(member.name, member_type="Student") | length %}
|
||||
{% set enrollment_suffix = _("Courses") if enrollment > 1 else _("Course") %}
|
||||
|
||||
<div class="container">
|
||||
<div class="profile-banner" style="background-image: url({{ cover_image }})">
|
||||
<div class="profile-avatar">
|
||||
{{ widgets.Avatar(member=member, avatar_class="avatar-square") }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile-info">
|
||||
<div class="profile-name-section">
|
||||
<div class="profile-name"> {{ member.full_name }} </div>
|
||||
|
||||
{% if get_authored_courses(member.name) | length %}
|
||||
<div class="creator-badge"> {{ _("Creator") }} </div>
|
||||
{% endif %}
|
||||
|
||||
{% if member.looking_for_job %}
|
||||
<div class="creator-badge"> {{ _("Open Network") }} </div>
|
||||
{% endif %}
|
||||
|
||||
{% if frappe.session.user == member.email %}
|
||||
<a class="button is-secondary ml-auto mt-1" href="/edit-profile?name={{ member.email }}"> {{ _("Edit Profile") }} </a>
|
||||
{% endif %}
|
||||
<div class="profile-banner" style="background-image: url({{ cover_image | urlencode }})">
|
||||
<div class="profile-avatar">
|
||||
{{ widgets.Avatar(member=member, avatar_class="avatar-square") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="profile-meta">
|
||||
{% if member.headline %}
|
||||
<div class="course-meta mr-3"> {{ member.headline }} </div>
|
||||
{% endif %}
|
||||
<div class="profile-info">
|
||||
<div class="profile-name-section">
|
||||
<div class="profile-name" data-name="{{ member.name }}"> {{ member.full_name }} </div>
|
||||
|
||||
{% if enrollment %}
|
||||
<div class="course-meta">
|
||||
<img src="/assets/lms/icons/book_plain.svg">
|
||||
{{ enrollment }} {{ enrollment_suffix }} {{ _("taken") }} </div>
|
||||
{% endif %}
|
||||
{% if courses_created | length %}
|
||||
<div class="creator-badge"> {{ _("Creator") }} </div>
|
||||
{% endif %}
|
||||
|
||||
{% if member.looking_for_job %}
|
||||
<div class="creator-badge"> {{ _("Open Network") }} </div>
|
||||
{% endif %}
|
||||
|
||||
{% if frappe.session.user == member.email %}
|
||||
<div class="ml-auto mt-1">
|
||||
<a class="btn btn-secondary btn-sm" href="/dashboard"> {{ _("Visit Dashboard") }} </a>
|
||||
<a class="btn btn-secondary btn-sm ml-2" href="/edit-profile/{{ member.email }}/edit"> {{ _("Edit Profile") }} </a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="profile-meta">
|
||||
{% if member.headline %}
|
||||
<div class="course-meta mr-3"> {{ member.headline }} </div>
|
||||
{% endif %}
|
||||
|
||||
{% if enrollment %}
|
||||
<div class="course-meta">
|
||||
<img src="/assets/lms/icons/book_plain.svg">
|
||||
{{ enrollment }} {{ enrollment_suffix }} {{ _("taken") }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro CoursesCreated(member, read_only) %}
|
||||
{% set authored_courses = get_authored_courses(member.name) %}
|
||||
{% if authored_courses | length %}
|
||||
<div class="profile-courses">
|
||||
<div class="course-home-headings"> {{ _("Courses Created") }} </div>
|
||||
<div class="cards-parent">
|
||||
{% for course in authored_courses %}
|
||||
{{ widgets.CourseCard(course=course, read_only=read_only) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
<!-- Courses Mentored -->
|
||||
{% macro CoursesMentored(member, read_only) %}
|
||||
{% if member.get_mentored_courses() | length %}
|
||||
<div class="profile-courses">
|
||||
<div class="course-home-headings"> {{ _("Courses Mentored") }} </div>
|
||||
<div class="cards-parent">
|
||||
{% for course in member.get_mentored_courses() %}
|
||||
{{ widgets.CourseCard(course=course, read_only=read_only) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="course-home-headings"> {{ _("Courses Mentored") }} </div>
|
||||
<div class="cards-parent">
|
||||
{% for course in member.get_mentored_courses() %}
|
||||
{{ widgets.CourseCard(course=course, read_only=read_only) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro CoursesEnrolled(member, read_only) %}
|
||||
{% set enrolled = get_enrolled_courses() %}
|
||||
|
||||
{% if enrolled.completed | length %}
|
||||
<div class="profile-courses">
|
||||
<div class="course-home-headings"> {{ _("Courses Completed") }} </div>
|
||||
<div class="cards-parent">
|
||||
{% for course in enrolled.completed %}
|
||||
{{ widgets.CourseCard(course=course, read_only=read_only) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if enrolled.in_progress | length %}
|
||||
<div class="profile-courses">
|
||||
<div class="course-home-headings"> {{ _("Courses In Progress") }} </div>
|
||||
<div class="cards-parent">
|
||||
{% for course in enrolled.in_progress %}
|
||||
{{ widgets.CourseCard(course=course, read_only=read_only) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
<!-- Profile Tabs Extension -->
|
||||
{% macro ProfileTabs(profile_tabs) %}
|
||||
<div>
|
||||
{% for tab in profile_tabs %}
|
||||
{% set slug = title.lower().replace(" ", "-") %}
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade py-4 show active" role="tabpanel" id="slug">
|
||||
{{ tab.render() }}
|
||||
{% for tab in profile_tabs %}
|
||||
{% set slug = title.lower().replace(" ", "-") %}
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade py-4 show active" role="tabpanel" id="slug">
|
||||
{{ tab.render() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro About(member) %}
|
||||
{% if member.bio %}
|
||||
<div class="">
|
||||
<div class="common-card-style profile-card">
|
||||
<div class="course-home-headings"> {{ _("About") }} </div>
|
||||
<div class="description">{{ member.bio }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Role Settings -->
|
||||
{% macro RoleSettings(member) %}
|
||||
{% if has_course_moderator_role() %}
|
||||
<div class="">
|
||||
<div class="common-card-style column-card">
|
||||
<div class="course-home-headings"> {{ _("Role Settings") }} </div>
|
||||
<div>
|
||||
<label class="role">
|
||||
<input type="checkbox" id="instructor" data-role="Course Instructor"
|
||||
{% if has_course_instructor_role(member.name) %} checked {% endif %}>
|
||||
{{ _("Course Instructor") }}
|
||||
</label>
|
||||
<label class="role">
|
||||
<input type="checkbox" id="moderator" data-role="Course Moderator"
|
||||
{% if has_course_moderator_role(member.name) %} checked {% endif %}>
|
||||
{{ _("Course Moderator") }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
<!-- About Section -->
|
||||
{% macro About(member) %}
|
||||
<div class="course-home-headings"> {{ _("About") }} </div>
|
||||
<div class="description">
|
||||
{% if member.bio %}
|
||||
{{ member.bio }}
|
||||
{% else %}
|
||||
{{ _("Hey, my name is ") }} {{ member.full_name }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
<!-- Work Preference -->
|
||||
{% macro WorkPreference(member) %}
|
||||
<div class="education-details">
|
||||
<div class="common-card-style profile-card">
|
||||
<div class="course-home-headings"> {{ _("Work Preference") }} </div>
|
||||
<div class="course-home-headings mt-10"> {{ _("Work Preference") }} </div>
|
||||
<div> {{ member.attire }} </div>
|
||||
<div> {{ member.collaboration }} </div>
|
||||
<div> {{ member.role }} </div>
|
||||
<div> {{ member.location_preference }} </div>
|
||||
<div> {{ member.time }} </div>
|
||||
<div> {{ member.company_type }} </div>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
<!-- Career Preference -->
|
||||
{% macro CareerPreference(member) %}
|
||||
{% if member.preferred_functions or member.preferred_industries or member.preferred_location or member.dream_companies %}
|
||||
<div class="education-details">
|
||||
<div class="common-card-style profile-card">
|
||||
<div class="course-home-headings"> {{ _("Career Preference") }} </div>
|
||||
{% if member.preferred_functions or member.preferred_industries or member.preferred_location or member.dream_companies %}
|
||||
<div class="course-home-headings mt-10"> {{ _("Career Preference") }} </div>
|
||||
<div class="profile-column-grid">
|
||||
{% if member.preferred_functions | length %}
|
||||
<div>
|
||||
<b>{{ _("Preferred Functions:") }}</b>
|
||||
{% for function in member.preferred_functions %}
|
||||
<div class="description">{{ function.function }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if member.preferred_industries | length %}
|
||||
<div>
|
||||
<b>{{ _("Preferred Industries:") }}</b>
|
||||
{% for industry in member.preferred_industries %}
|
||||
<div class="description">{{ industry.industry }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if member.preferred_functions | length %}
|
||||
<div>
|
||||
<b>{{ _("Preferred Functions:") }}</b>
|
||||
{% for function in member.preferred_functions %}
|
||||
<div class="description">{{ function.function }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if member.preferred_location %}
|
||||
<div>
|
||||
<b> {{ _("Preferred Locations:") }} </b>
|
||||
<div class="description"> {{ member.preferred_location }} </div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if member.preferred_industries | length %}
|
||||
<div>
|
||||
<b>{{ _("Preferred Industries:") }}</b>
|
||||
{% for industry in member.preferred_industries %}
|
||||
<div class="description">{{ industry.industry }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if member.preferred_location %}
|
||||
<div>
|
||||
<b> {{ _("Preferred Locations:") }} </b>
|
||||
<div class="description"> {{ member.preferred_location }} </div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if member.dream_companies %}
|
||||
<div>
|
||||
<b> {{ _("Dream Companies:") }} </b>
|
||||
<div class="description"> {{ member.dream_companies }} </div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if member.dream_companies %}
|
||||
<div>
|
||||
<b> {{ _("Dream Companies:") }} </b>
|
||||
<div class="description"> {{ member.dream_companies }} </div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
<!-- Contact Section -->
|
||||
{% macro Contact(member) %}
|
||||
{% if member.linkedin or member.medium or member.github %}
|
||||
<div class="education-details">
|
||||
<div class="common-card-style profile-card">
|
||||
<div class="course-home-headings"> {{ _("Contact") }} </div>
|
||||
{% if member.linkedin or member.medium or member.github %}
|
||||
<div class="course-home-headings mt-10"> {{ _("Contact") }} </div>
|
||||
<div class="profile-column-grid">
|
||||
{% if member.linkedin %}
|
||||
{% set linkedin = member.linkedin[:-1] if member.linkedin[-1] == "/" else member.linkedin %}
|
||||
<a class="button-links description" href="{{ member.linkedin }}">
|
||||
{% if member.linkedin %}
|
||||
{% set linkedin = member.linkedin[:-1] if member.linkedin[-1] == "/" else member.linkedin %}
|
||||
<a class="button-links description" href="{{ member.linkedin }}">
|
||||
<img src="/assets/lms/icons/linkedin.svg"> {{ linkedin.split("/")[-1] }}
|
||||
</a>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if member.medium %}
|
||||
<a class="button-links description" href="{{ member.medium}}">
|
||||
<img src="/assets/lms/icons/medium.svg"> {{ member.medium.split("/")[-1] }}
|
||||
<img src="/assets/lms/icons/medium.svg"> {{ member.medium.split("/")[-1] }}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if member.github %}
|
||||
<a class="button-links description" href="{{ member.github }}">
|
||||
<img src="/assets/lms/icons/github.svg"> {{ member.github.split("/")[-1] }}
|
||||
<img src="/assets/lms/icons/github.svg"> {{ member.github.split("/")[-1] }}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
<!-- Skills -->
|
||||
{% macro Skills(member) %}
|
||||
{% if member.skill | length %}
|
||||
<div class="education-details">
|
||||
<div class="common-card-style profile-card">
|
||||
<div class="course-home-headings"> {{ _("Skills")}} </div>
|
||||
{% if member.skill | length %}
|
||||
<div class="course-home-headings mt-10"> {{ _("Skills")}} </div>
|
||||
<div class="profile-column-grid">
|
||||
{% for skill in member.skill %}
|
||||
<div class="description"> {{ skill.skill_name }} </div>
|
||||
{% endfor %}
|
||||
{% for skill in member.skill %}
|
||||
<div class="description"> {{ skill.skill_name }} </div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
<!-- Education Details -->
|
||||
{% macro EducationDetails(member) %}
|
||||
{% if member.education %}
|
||||
<div class="education-details">
|
||||
<div class="common-card-style profile-card">
|
||||
<div class="course-home-headings"> {{ _("Education") }} </div>
|
||||
{% if member.education %}
|
||||
<div class="course-home-headings mt-10"> {{ _("Education") }} </div>
|
||||
<div class="profile-grid-card">
|
||||
{% for edu in member.education %}
|
||||
<div class="profile-card-row">
|
||||
<div class="bold-title"> {{ edu.institution_name }} </div>
|
||||
<div class="profile-item"> {{ edu.degree_type }} <span></span> {{ edu.major }}
|
||||
{% if not member.hide_private %}
|
||||
<!-- {% if edu.grade_type %} {{ edu.grade_type }} {% endif %} -->
|
||||
{% if edu.grade %} <span></span> {{ edu.grade }} {% endif %}
|
||||
{% endif %}
|
||||
{% for edu in member.education %}
|
||||
<div class="column-card-row">
|
||||
<div class="bold-title"> {{ edu.institution_name }} </div>
|
||||
<div class="profile-item"> {{ edu.degree_type }} <span></span> {{ edu.major }}
|
||||
{% if not member.hide_private %}
|
||||
<!-- {% if edu.grade_type %} {{ edu.grade_type }} {% endif %} -->
|
||||
{% if edu.grade %} <span></span> {{ edu.grade }} {% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="description">
|
||||
{% if edu.start_date %}
|
||||
{{ frappe.utils.format_date(edu.start_date, "MMM YYYY") }} -
|
||||
{% endif %}
|
||||
{{ frappe.utils.format_date(edu.end_date, "MMM YYYY") }}
|
||||
</div>
|
||||
<div class="description"> {{ edu.location }} </div>
|
||||
</div>
|
||||
<div class="description">
|
||||
{% if edu.start_date %}
|
||||
{{ frappe.utils.format_date(edu.start_date, "MMM YYYY") }} -
|
||||
{% endif %}
|
||||
{{ frappe.utils.format_date(edu.end_date, "MMM YYYY") }} </div>
|
||||
<div class="description"> {{ edu.location }} </div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
<!-- Work Details -->
|
||||
{% macro WorkDetails(member) %}
|
||||
{% set work_details = member.work_experience + member.internship %}
|
||||
{% if work_details | length %}
|
||||
<div class="education-details">
|
||||
<div class="common-card-style profile-card">
|
||||
<div class="course-home-headings"> {{ _("Work Experience") }} </div>
|
||||
<div class="profile-grid-card">
|
||||
{% for work in work_details %}
|
||||
<div class="">
|
||||
<div class="bold-title"> {{ work.title }} </div>
|
||||
<div class="profile-item"> {{ work.company }} </div>
|
||||
<div class="description"> {{ frappe.utils.format_date(work.from_date, "MMM YYYY") }} -
|
||||
{% if work.to_date %} {{ frappe.utils.format_date(work.to_date, "MMM YYYY") }} {% else %} Present {% endif %} </div>
|
||||
<div class="description"> {{ work.location }} </div>
|
||||
{% if work.description %} <div class="profile-item"> {{ work.description }} </div> {% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
{% set work_details = member.work_experience + member.internship %}
|
||||
|
||||
{% macro Certification(member) %}
|
||||
{% if member.certification %}
|
||||
<div class="education-details">
|
||||
<div class="common-card-style profile-card">
|
||||
<div class="course-home-headings"> {{ _("Certification") }} </div>
|
||||
{% if work_details | length %}
|
||||
<div class="course-home-headings mt-10"> {{ _("Work Experience") }} </div>
|
||||
<div class="profile-grid-card">
|
||||
{% for cert in member.certification %}
|
||||
<div class="">
|
||||
<div class="bold-title"> {{ cert.certification_name }} </div>
|
||||
<div class="profile-item"> {{ cert.organization }} </div>
|
||||
<div class="description"> {{ frappe.utils.format_date(cert.issue_date, "MMM YYYY") }}
|
||||
{% if cert.expiration_date %} - {{ frappe.utils.format_date(cert.expiration_date, "MMM YYYY") }} {% endif %} </div>
|
||||
{% if cert.description %} <div class="profile-item"> {{ cert.description }} </div> {% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% for work in work_details %}
|
||||
<div class="">
|
||||
<div class="bold-title"> {{ work.title }} </div>
|
||||
<div class="profile-item"> {{ work.company }} </div>
|
||||
<div class="description">
|
||||
{{ frappe.utils.format_date(work.from_date, "MMM YYYY") }} -
|
||||
{% if work.to_date %} {{ frappe.utils.format_date(work.to_date, "MMM YYYY") }}
|
||||
{% else %} Present {% endif %}
|
||||
</div>
|
||||
|
||||
<div class="description"> {{ work.location }} </div>
|
||||
|
||||
{% if work.description %}
|
||||
<div class="profile-item">
|
||||
{{ work.description }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% block script %}
|
||||
<script>
|
||||
frappe.ready(() => {
|
||||
if ("{{ member.name }}" == frappe.session.user) {
|
||||
setTimeout(() => {
|
||||
var link_array = $('.nav-link').filter((i, elem) => $(elem).text().trim() === "My Profile");
|
||||
link_array.length && $(link_array[0]).addClass("active");
|
||||
}, 0)
|
||||
}
|
||||
|
||||
if ($(".profile-column-one").children().length == 0) {
|
||||
$(".profile-column-one").hide();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
<!-- Certifications -->
|
||||
{% macro ExternalCertification(member) %}
|
||||
{% if member.certification %}
|
||||
<div class="course-home-headings mt-10"> {{ _("External Certification") }} </div>
|
||||
<div class="profile-grid-card">
|
||||
{% for cert in member.certification %}
|
||||
<div class="">
|
||||
|
||||
<div class="bold-title"> {{ cert.certification_name }} </div>
|
||||
<div class="profile-item"> {{ cert.organization }} </div>
|
||||
|
||||
<div class="description">
|
||||
{{ frappe.utils.format_date(cert.issue_date, "MMM YYYY") }}
|
||||
{% if cert.expiration_date %}
|
||||
- {{ frappe.utils.format_date(cert.expiration_date, "MMM YYYY") }}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if cert.description %}
|
||||
<div class="profile-item">
|
||||
{{ cert.description }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
42
lms/www/profiles/profile.js
Normal file
42
lms/www/profiles/profile.js
Normal file
@@ -0,0 +1,42 @@
|
||||
frappe.ready(() => {
|
||||
|
||||
make_profile_active_in_navbar();
|
||||
|
||||
$(".role").change((e) => {
|
||||
save_role(e);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
const make_profile_active_in_navbar = () => {
|
||||
let member_name = $(".profile-name").data("name");
|
||||
if (member_name == frappe.session.user) {
|
||||
setTimeout(() => {
|
||||
let link_array = $('.nav-link').filter((i, elem) => $(elem).text().trim() === "My Profile");
|
||||
link_array.length && $(link_array[0]).addClass("active");
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const save_role = (e) => {
|
||||
let member_name = $(".profile-name").data("name");
|
||||
let role = $(e.currentTarget).children("input");
|
||||
frappe.call({
|
||||
method: "lms.overrides.user.save_role",
|
||||
args: {
|
||||
"user": member_name,
|
||||
"role": role.data("role"),
|
||||
"value": role.prop("checked") ? 1 : 0
|
||||
},
|
||||
callback: (data) => {
|
||||
if (data.message) {
|
||||
frappe.show_alert({
|
||||
message: __("Saved"),
|
||||
indicator: "green",
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import frappe
|
||||
from lms.page_renderers import get_profile_url_prefix
|
||||
from urllib.parse import urlencode
|
||||
|
||||
|
||||
def get_context(context):
|
||||
context.no_cache = 1
|
||||
@@ -12,13 +12,16 @@ def get_context(context):
|
||||
if username:
|
||||
frappe.local.flags.redirect_location = get_profile_url_prefix() + username
|
||||
raise frappe.Redirect
|
||||
|
||||
try:
|
||||
context.member = frappe.get_doc("User", {"username": username})
|
||||
except:
|
||||
context.template = "www/404.html"
|
||||
return
|
||||
|
||||
context.profile_tabs = get_profile_tabs(context.member)
|
||||
|
||||
|
||||
def get_profile_tabs(user):
|
||||
"""Returns the enabled ProfileTab objects.
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ def get_common_context(context):
|
||||
batch_name = None
|
||||
|
||||
course = frappe.db.get_value("LMS Course",
|
||||
frappe.form_dict["course"], ["name", "title", "video_link", "enable_certification"], as_dict=True)
|
||||
frappe.form_dict["course"], ["name", "title", "video_link", "enable_certification", "status"], as_dict=True)
|
||||
if not course:
|
||||
context.template = "www/404.html"
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user