feat: access for course creation

This commit is contained in:
Jannat Patel
2022-08-22 12:58:54 +05:30
parent 5aba690318
commit 668130d443
17 changed files with 123 additions and 92 deletions

View File

@@ -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": []
}

View File

@@ -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"})

View File

@@ -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",

View File

@@ -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")

View File

@@ -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

View 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)

View File

@@ -944,7 +944,7 @@ pre {
float: right;
width: 80%;
display: flex;
justify-content: end;
justify-content: flex-end;
align-items: center;
}

View File

@@ -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">

View File

@@ -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 %}

View File

@@ -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">

View File

@@ -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">&times;</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">&times;</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 %}

View File

@@ -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">

View File

@@ -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))
};

View File

@@ -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

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}