feat: access for course creation
This commit is contained in:
@@ -191,7 +191,8 @@ jinja = {
|
||||
"lms.lms.utils.get_signup_optin_checks",
|
||||
"lms.lms.utils.get_popular_courses",
|
||||
"lms.lms.utils.format_amount",
|
||||
"lms.lms.utils.first_lesson_exists"
|
||||
"lms.lms.utils.first_lesson_exists",
|
||||
"lms.lms.utils.has_course_instructor_role"
|
||||
],
|
||||
"filters": []
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ class TestLMSQuiz(unittest.TestCase):
|
||||
}).save(ignore_permissions=True)
|
||||
|
||||
def test_with_multiple_options(self):
|
||||
quiz = frappe.get_doc("LMS Quiz", "Test Quiz")
|
||||
quiz = frappe.get_doc("LMS Quiz", "test-quiz")
|
||||
quiz.append("questions", {
|
||||
"question": "Question multiple",
|
||||
"option_1": "Option 1",
|
||||
@@ -27,7 +27,7 @@ class TestLMSQuiz(unittest.TestCase):
|
||||
self.assertTrue(quiz.questions[0].multiple)
|
||||
|
||||
def test_with_no_correct_option(self):
|
||||
quiz = frappe.get_doc("LMS Quiz", "Test Quiz")
|
||||
quiz = frappe.get_doc("LMS Quiz", "test-quiz")
|
||||
quiz.append("questions", {
|
||||
"question": "Question no correct option",
|
||||
"option_1": "Option 1",
|
||||
@@ -37,5 +37,5 @@ class TestLMSQuiz(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls) -> None:
|
||||
frappe.db.delete("LMS Quiz", "Test Quiz")
|
||||
frappe.db.delete("LMS Quiz Question", {"parent": "Test Quiz"})
|
||||
frappe.db.delete("LMS Quiz", "test-quiz")
|
||||
frappe.db.delete("LMS Quiz Question", {"parent": "test-quiz"})
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"show_search",
|
||||
"portal_course_creation",
|
||||
"force_profile_completion",
|
||||
"portal_course_creation",
|
||||
"column_break_2",
|
||||
"search_placeholder",
|
||||
"custom_certificate_template",
|
||||
@@ -110,10 +110,11 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"default": "Course Instructor Role",
|
||||
"fieldname": "portal_course_creation",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Course Creation from Portal"
|
||||
"fieldtype": "Select",
|
||||
"label": "Course Creation Access Through Website To",
|
||||
"options": "Course Instructor Role\nAnyone"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_9",
|
||||
@@ -149,7 +150,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2022-05-09 09:55:24.519269",
|
||||
"modified": "2022-08-22 10:02:59.988499",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Settings",
|
||||
|
||||
@@ -400,3 +400,10 @@ def first_lesson_exists(course):
|
||||
def redirect_to_courses_list():
|
||||
frappe.local.flags.redirect_location = "/courses"
|
||||
raise frappe.Redirect
|
||||
|
||||
|
||||
def has_course_instructor_role():
|
||||
return frappe.db.get_value("Has Role", {
|
||||
"parent": frappe.session.user,
|
||||
"role": "Course Instructor"
|
||||
}, "name")
|
||||
|
||||
@@ -30,3 +30,4 @@ 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
|
||||
lms.patches.v0_0.create_course_instructor_role
|
||||
|
||||
10
lms/patches/v0_0/create_course_instructor_role.py
Normal file
10
lms/patches/v0_0/create_course_instructor_role.py
Normal file
@@ -0,0 +1,10 @@
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
if not frappe.db.exists("Role", "Course Instructor"):
|
||||
role = frappe.get_doc({
|
||||
"doctype": "Role",
|
||||
"role_name": "Course Instructor",
|
||||
"home_page": "/dashboard",
|
||||
})
|
||||
role.save(ignore_permissions=True)
|
||||
@@ -944,7 +944,7 @@ pre {
|
||||
float: right;
|
||||
width: 80%;
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ const add_chapter = (e) => {
|
||||
return;
|
||||
}
|
||||
|
||||
let next_index = $("[data-index]").last().data("index") || 1;
|
||||
let next_index = $("[data-index]").last().data("index") + 1 || 1;
|
||||
let add_after = $(`.chapter-parent:last`).length ? $(`.chapter-parent:last`) : $("#outline-heading");
|
||||
console.log(add_after)
|
||||
$(`<div class="chapter-parent chapter-edit new-chapter">
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
{% set courses = get_authored_courses(frappe.session.user, only_published=False) %}
|
||||
|
||||
{% if courses | length %}
|
||||
<div class="cards-parent">
|
||||
{% for course in courses %}
|
||||
{{ widgets.CourseCard(course=course) }}
|
||||
{% endfor %}
|
||||
{% for course in courses %}
|
||||
{{ widgets.CourseCard(course=course) }}
|
||||
{% 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">{{ _("No courses created") }}</div>
|
||||
<div class="course-meta">{{ _("Help others learn something new.") }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<a class="button is-secondary button-links" href="/course?new=1">
|
||||
{{ _("Create a Course") }}
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<img class="icon icon-xl" src="/assets/lms/icons/comment.svg">
|
||||
</div>
|
||||
<div class="empty-state-text">
|
||||
<div class="empty-state-heading">{{ _("No courses created") }}</div>
|
||||
<div class="course-meta">{{ _("Help others learn something new.") }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
<div> {{ _("Your latest score is {0}.").format(last_attempt_score) }} </div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div id="quiz-title" class="hide" data-max-attempts="{{ quiz.max_attempts }}">{{ quiz.title }}</div>
|
||||
<div id="quiz-title" class="hide" data-name="{{ quiz.name }}"
|
||||
data-max-attempts="{{ quiz.max_attempts }}">{{ quiz.title }}</div>
|
||||
|
||||
<div class="">
|
||||
<div id="start-banner" class="text-center">
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
{% set show_search = frappe.db.get_single_value("LMS Settings", "show_search") %}
|
||||
{% set search_placeholder = frappe.db.get_single_value("LMS Settings", "search_placeholder") %}
|
||||
{% set portal_course_creation = frappe.db.get_single_value("LMS Settings", "portal_course_creation") %}
|
||||
|
||||
{% if show_search %}
|
||||
<input class="search" id="search-course" placeholder="{{ _(search_placeholder) }}">
|
||||
<div class="course-search-header">
|
||||
<input class="search" id="search-course" placeholder="{{ _(search_placeholder) }}">
|
||||
{% if portal_course_creation == "Anyone" or has_course_instructor_role() %}
|
||||
<a class="btn btn-secondary btn-md ml-2" href="/courses/new-course"> {{ _("Create a Course") }} </a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="empty-state alert alert-dismissible search-empty-state hide">
|
||||
<a href="#" class="close-search-empty-state" aria-label="close">×</a>
|
||||
<div>
|
||||
<img class="icon icon-xl" src="/assets/frappe/images/ui-states/search-empty-state.svg">
|
||||
</div>
|
||||
<div class="empty-state-text">
|
||||
<div class="empty-state-heading">{{ _("No results found") }}</div>
|
||||
<div class="course-meta">{{ _("Try some other keyword or explore our list of courses.") }}</div>
|
||||
</div>
|
||||
<a href="#" class="close-search-empty-state" aria-label="close">×</a>
|
||||
<div>
|
||||
<img class="icon icon-xl" src="/assets/frappe/images/ui-states/search-empty-state.svg">
|
||||
</div>
|
||||
<div class="empty-state-text">
|
||||
<div class="empty-state-heading">{{ _("No results found") }}</div>
|
||||
<div class="course-meta">{{ _("Try some other keyword or explore our list of courses.") }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script> {% include "lms/templates/search_course/search_course.js" %} </script>
|
||||
{% endif %}
|
||||
|
||||
@@ -185,7 +185,9 @@
|
||||
|
||||
<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>
|
||||
{% endif %}
|
||||
<a class="btn btn-secondary btn-sm pull-right" href="/quizzes"> {{ _("Create a Quiz") }} </a>
|
||||
|
||||
<div class="attachments-parent">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
frappe.ready(() => {
|
||||
|
||||
localStorage.removeItem($("#quiz-title").text());
|
||||
localStorage.removeItem($("#quiz-title").data("name"));
|
||||
|
||||
fetch_assignments();
|
||||
|
||||
@@ -200,8 +200,9 @@ const move_to_next_lesson = (status, e) => {
|
||||
|
||||
const quiz_summary = (e=undefined) => {
|
||||
e && e.preventDefault();
|
||||
let quiz_name = $("#quiz-title").text();
|
||||
let quiz_name = $("#quiz-title").data("name");
|
||||
let total_questions = $(".question").length;
|
||||
|
||||
frappe.call({
|
||||
method: "lms.lms.doctype.lms_quiz.lms_quiz.quiz_summary",
|
||||
args: {
|
||||
@@ -230,7 +231,6 @@ const check_answer = (e=undefined) => {
|
||||
e && e.preventDefault();
|
||||
clearInterval(self.timer);
|
||||
$(".timer").addClass("hide");
|
||||
let quiz_name = $("#quiz-title").text();
|
||||
let total_questions = $(".question").length;
|
||||
let current_index = $(".active-question").attr("data-qt-index");
|
||||
|
||||
@@ -249,7 +249,7 @@ const check_answer = (e=undefined) => {
|
||||
$("#next").removeClass("hide");
|
||||
}
|
||||
let [answer, is_correct] = parse_options();
|
||||
add_to_local_storage(quiz_name, current_index, answer, is_correct);
|
||||
add_to_local_storage(current_index, answer, is_correct);
|
||||
};
|
||||
|
||||
|
||||
@@ -285,13 +285,16 @@ const add_icon = (element, icon) => {
|
||||
};
|
||||
|
||||
|
||||
const add_to_local_storage = (quiz_name, current_index, answer, is_correct) => {
|
||||
const add_to_local_storage = (current_index, answer, is_correct) => {
|
||||
let quiz_name = $("#quiz-title").data("name")
|
||||
let quiz_stored = JSON.parse(localStorage.getItem(quiz_name));
|
||||
|
||||
let quiz_obj = {
|
||||
"question_index": current_index,
|
||||
"answer": answer.join(),
|
||||
"is_correct": is_correct
|
||||
}
|
||||
|
||||
quiz_stored ? quiz_stored.push(quiz_obj) : quiz_stored = [quiz_obj]
|
||||
localStorage.setItem(quiz_name, JSON.stringify(quiz_stored))
|
||||
};
|
||||
|
||||
@@ -24,16 +24,17 @@ def get_context(context):
|
||||
|
||||
context.lesson = get_current_lesson_details(lesson_number, context)
|
||||
if not context.lesson:
|
||||
context.lessom = frappe._dict()
|
||||
context.lesson = frappe._dict()
|
||||
|
||||
if frappe.form_dict.get("edit"):
|
||||
if not is_instructor(context.course.name):
|
||||
redirect_to_courses_list()
|
||||
context.lesson.edit_mode = True
|
||||
else:
|
||||
neighbours = get_neighbours(lesson_number, context.lessons)
|
||||
context.next_url = get_url(neighbours["next"], context.course)
|
||||
context.prev_url = get_url(neighbours["prev"], context.course)
|
||||
|
||||
neighbours = get_neighbours(lesson_number, context.lessons)
|
||||
context.next_url = get_url(neighbours["next"], context.course)
|
||||
context.prev_url = get_url(neighbours["prev"], context.course)
|
||||
meta_info = context.lesson.title + " - " + context.course.title if context.lesson.title else "New Lesson"
|
||||
context.metatags = {
|
||||
"title": meta_info,
|
||||
@@ -52,8 +53,13 @@ def get_context(context):
|
||||
|
||||
def get_current_lesson_details(lesson_number, context):
|
||||
details_list = list(filter(lambda x: cstr(x.number) == lesson_number, context.lessons))
|
||||
|
||||
if not len(details_list):
|
||||
redirect_to_lesson(context.course)
|
||||
if frappe.form_dict.get("edit"):
|
||||
return None
|
||||
else:
|
||||
redirect_to_lesson(context.course)
|
||||
|
||||
lesson_info = details_list[0]
|
||||
lesson_info.body = lesson_info.body.replace("\"", "'")
|
||||
return lesson_info
|
||||
|
||||
@@ -188,10 +188,13 @@ data-placeholder="Description">{% if course.description %}{{ frappe.utils.md_to_
|
||||
{% if course.edit_mode %}
|
||||
<div class="my-4">
|
||||
<button class="btn btn-primary btn-md btn-save-course">
|
||||
{{ _("Save Course Details") }} </button>
|
||||
{{ _("Save Course Details") }}
|
||||
</button>
|
||||
{% if course.name %}
|
||||
<a class="btn btn-secondary btn-md btn-exit-edit ml-2" href="/courses/{{ course.name }}">
|
||||
{{ _("Back to Course") }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
@@ -15,11 +15,7 @@
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
|
||||
<div class="course-search-header">
|
||||
{% include "lms/templates/search_course/search_course.html" %}
|
||||
<a class="btn btn-secondary btn-md ml-2" href="/courses/new-course"> {{ _("Create a Course") }} </a>
|
||||
</div>
|
||||
{% include "lms/templates/search_course/search_course.html" %}
|
||||
|
||||
<div class="course-list">
|
||||
{% set courses = live_courses %}
|
||||
|
||||
@@ -2,48 +2,48 @@
|
||||
{% 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 portal_course_creation %}
|
||||
<a class="button is-default button-links pull-right hide" id="create-course-link" href="/courses/new-course">
|
||||
<svg class="icon icon-sm mr-1"><use href="#icon-add"></use></svg>
|
||||
{{ _("New Course")}}
|
||||
</a>
|
||||
{% endif %}
|
||||
<ul class="nav" id="courses-tab">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" data-toggle="tab" href="#courses-enrolled"> {{ _("Courses Enrolled") }} </a>
|
||||
</li>
|
||||
{% if portal_course_creation %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#courses-created">{{ _("Courses Created") }}
|
||||
<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>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<div class="border-bottom mb-4"></div>
|
||||
<div class="tab-content">
|
||||
<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 portal_course_creation %}
|
||||
<div class="tab-pane fade" id="courses-created" role="tabpanel" aria-labelledby="courses-created">
|
||||
{% include "lms/templates/courses_created.html" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<ul class="nav" id="courses-tab">
|
||||
<li class="nav-item">
|
||||
<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>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
<div class="border-bottom mb-4"></div>
|
||||
|
||||
<div class="tab-content">
|
||||
<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 %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
|
||||
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
|
||||
<script>
|
||||
frappe.ready(() => {
|
||||
$('#courses-tab a[data-toggle="tab"]').on('shown.bs.tab', (e) => {
|
||||
let link = $("#create-course-link");
|
||||
$(e.currentTarget).attr("href") == "#courses-created" ? link.removeClass("hide") : link.addClass("hide");
|
||||
});
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user