chore: resolved conflicts

This commit is contained in:
Jannat Patel
2023-10-16 11:19:34 +05:30
26 changed files with 270 additions and 91 deletions

View File

@@ -78,4 +78,3 @@ jobs:
- name: run tests - name: run tests
working-directory: /home/runner/frappe-bench working-directory: /home/runner/frappe-bench
run: bench --site frappe.local run-tests --app lms run: bench --site frappe.local run-tests --app lms

View File

@@ -3,7 +3,7 @@
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
from lms.lms.utils import can_create_courses from lms.lms.utils import has_course_moderator_role, has_course_instructor_role
class LMSAssignment(Document): class LMSAssignment(Document):
@@ -12,7 +12,7 @@ class LMSAssignment(Document):
@frappe.whitelist() @frappe.whitelist()
def save_assignment(assignment, title, type, question): def save_assignment(assignment, title, type, question):
if not can_create_courses(): if not has_course_moderator_role() or not has_course_instructor_role():
return return
if assignment: if assignment:

View File

@@ -12,12 +12,16 @@ frappe.ui.form.on("LMS Batch", {
}); });
frm.set_query("reference_doctype", "timetable", function () { frm.set_query("reference_doctype", "timetable", function () {
let doctypes = [ let doctypes = ["Course Lesson", "LMS Quiz", "LMS Assignment"];
"Course Lesson", return {
"LMS Quiz", filters: {
"LMS Assignment", name: ["in", doctypes],
"LMS Live Class", },
]; };
});
frm.set_query("reference_doctype", "timetable_legends", function () {
let doctypes = ["Course Lesson", "LMS Quiz", "LMS Assignment"];
return { return {
filters: { filters: {
name: ["in", doctypes], name: ["in", doctypes],
@@ -27,6 +31,11 @@ frappe.ui.form.on("LMS Batch", {
}, },
timetable_template: function (frm) { timetable_template: function (frm) {
set_timetable(frm);
},
});
const set_timetable = (frm) => {
if (frm.doc.timetable_template) { if (frm.doc.timetable_template) {
frm.clear_table("timetable"); frm.clear_table("timetable");
frm.refresh_fields(); frm.refresh_fields();
@@ -46,6 +55,7 @@ frappe.ui.form.on("LMS Batch", {
], ],
filters: { filters: {
parent: frm.doc.timetable_template, parent: frm.doc.timetable_template,
parenttype: "LMS Timetable Template",
}, },
order_by: "idx", order_by: "idx",
}, },
@@ -54,8 +64,7 @@ frappe.ui.form.on("LMS Batch", {
}, },
}); });
} }
}, };
});
const add_timetable_rows = (frm, timetable) => { const add_timetable_rows = (frm, timetable) => {
timetable.forEach((row) => { timetable.forEach((row) => {
@@ -75,5 +84,40 @@ const add_timetable_rows = (frm, timetable) => {
child.duration = row.duration; child.duration = row.duration;
}); });
frm.refresh_field("timetable"); frm.refresh_field("timetable");
set_legends(frm);
};
const set_legends = (frm) => {
if (frm.doc.timetable_template) {
frm.clear_table("timetable_legends");
frm.refresh_fields();
frappe.call({
method: "frappe.client.get_list",
args: {
doctype: "LMS Timetable Legend",
parent: "LMS Timetable Template",
fields: ["reference_doctype", "label", "color"],
filters: {
parent: frm.doc.timetable_template,
parenttype: "LMS Timetable Template",
},
order_by: "idx",
},
callback: (data) => {
add_legend_rows(frm, data.message);
},
});
}
};
const add_legend_rows = (frm, legends) => {
legends.forEach((row) => {
let child = frm.add_child("timetable_legends");
child.reference_doctype = row.reference_doctype;
child.label = row.label;
child.color = row.color;
});
frm.refresh_field("timetable_legends");
frm.save(); frm.save();
}; };

View File

@@ -35,8 +35,10 @@
"timetable_template", "timetable_template",
"column_break_anya", "column_break_anya",
"show_live_class", "show_live_class",
"allow_future",
"section_break_ontp", "section_break_ontp",
"timetable", "timetable",
"timetable_legends",
"pricing_tab", "pricing_tab",
"section_break_gsac", "section_break_gsac",
"paid_batch", "paid_batch",
@@ -220,7 +222,7 @@
"default": "0", "default": "0",
"fieldname": "show_live_class", "fieldname": "show_live_class",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Show Live Class" "label": "Show live class"
}, },
{ {
"fieldname": "section_break_ontp", "fieldname": "section_break_ontp",
@@ -263,11 +265,23 @@
"fieldtype": "Code", "fieldtype": "Code",
"label": "Custom Script (JavaScript)", "label": "Custom Script (JavaScript)",
"options": "Javascript" "options": "Javascript"
},
{
"fieldname": "timetable_legends",
"fieldtype": "Table",
"label": "Timetable Legends",
"options": "LMS Timetable Legend"
},
{
"default": "1",
"fieldname": "allow_future",
"fieldtype": "Check",
"label": "Allow accessing future dates"
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2023-09-28 12:18:34.418812", "modified": "2023-10-12 12:53:37.351989",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "LMS", "module": "LMS",
"name": "LMS Batch", "name": "LMS Batch",

View File

@@ -216,7 +216,7 @@ def save_course(
course_price=None, course_price=None,
currency=None, currency=None,
): ):
if not can_create_courses(): if not can_create_courses(course):
return return
if course: if course:

View File

@@ -8,7 +8,7 @@ from frappe.model.document import Document
class LMSQuestion(Document): class LMSQuestion(Document):
def validate(self): def validate(self):
self.validate_correct_answers() validate_correct_answers(self)
def validate_correct_answers(question): def validate_correct_answers(question):

View File

@@ -6,8 +6,12 @@ import frappe
from frappe import _ from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import cstr from frappe.utils import cstr
from lms.lms.utils import generate_slug, has_course_moderator_role, can_create_courses
from lms.lms.doctype.lms_question.lms_question import validate_correct_answers from lms.lms.doctype.lms_question.lms_question import validate_correct_answers
from lms.lms.utils import (
generate_slug,
has_course_moderator_role,
has_course_instructor_role,
)
class LMSQuiz(Document): class LMSQuiz(Document):
@@ -74,7 +78,7 @@ def quiz_summary(quiz, results):
def save_quiz( def save_quiz(
quiz_title, max_attempts=1, quiz=None, show_answers=1, show_submission_history=0 quiz_title, max_attempts=1, quiz=None, show_answers=1, show_submission_history=0
): ):
if not can_create_courses(): if not has_course_moderator_role() or not has_course_instructor_role():
return return
values = { values = {

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2023, Frappe and contributors
// For license information, please see license.txt
// frappe.ui.form.on("LMS Timetable Legend", {
// refresh(frm) {
// },
// });

View File

@@ -0,0 +1,52 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "hash",
"creation": "2023-10-11 16:36:45.079267",
"default_view": "List",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"reference_doctype",
"label",
"color"
],
"fields": [
{
"fieldname": "reference_doctype",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Reference DocType",
"options": "DocType",
"reqd": 1
},
{
"fieldname": "color",
"fieldtype": "Color",
"in_list_view": 1,
"label": "Color",
"reqd": 1
},
{
"fieldname": "label",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Label",
"reqd": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2023-10-11 17:15:37.039139",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Timetable Legend",
"naming_rule": "Random",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2023, Frappe and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class LMSTimetableLegend(Document):
pass

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2023, Frappe and Contributors
# See license.txt
# import frappe
from frappe.tests.utils import FrappeTestCase
class TestLMSTimetableLegend(FrappeTestCase):
pass

View File

@@ -11,5 +11,19 @@ frappe.ui.form.on("LMS Timetable Template", {
}, },
}; };
}); });
frm.set_query("reference_doctype", "timetable_legends", function () {
let doctypes = [
"Course Lesson",
"LMS Quiz",
"LMS Assignment",
"LMS Live Class",
];
return {
filters: {
name: ["in", doctypes],
},
};
});
}, },
}); });

View File

@@ -8,7 +8,8 @@
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"title", "title",
"timetable" "timetable",
"timetable_legends"
], ],
"fields": [ "fields": [
{ {
@@ -21,11 +22,17 @@
"fieldtype": "Table", "fieldtype": "Table",
"label": "Timetable", "label": "Timetable",
"options": "LMS Batch Timetable" "options": "LMS Batch Timetable"
},
{
"fieldname": "timetable_legends",
"fieldtype": "Table",
"label": "Timetable Legends",
"options": "LMS Timetable Legend"
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2023-09-18 17:57:15.819072", "modified": "2023-10-11 17:09:05.096243",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "LMS", "module": "LMS",
"name": "LMS Timetable Template", "name": "LMS Timetable Template",

View File

@@ -521,21 +521,35 @@ def has_course_instructor_role(member=None):
) )
def can_create_courses(member=None): def can_create_courses(course, member=None):
if not member: if not member:
member = frappe.session.user member = frappe.session.user
instructors = frappe.get_all(
"Course Instructor",
{
"parent": course,
},
pluck="instructor",
)
if frappe.session.user == "Guest": if frappe.session.user == "Guest":
return False return False
if has_course_instructor_role(member) or has_course_moderator_role(member): if has_course_moderator_role(member):
return True
if has_course_instructor_role(member) and member in instructors:
return True return True
portal_course_creation = frappe.db.get_single_value( portal_course_creation = frappe.db.get_single_value(
"LMS Settings", "portal_course_creation" "LMS Settings", "portal_course_creation"
) )
return portal_course_creation == "Anyone" if portal_course_creation == "Anyone" and member in instructors:
return True
return False
def has_course_moderator_role(member=None): def has_course_moderator_role(member=None):
@@ -727,7 +741,7 @@ def get_chart_data(chart_name, timespan, timegrain, from_date, to_date):
} }
@frappe.whitelist() @frappe.whitelist(allow_guest=True)
def get_course_completion_data(): def get_course_completion_data():
all_membership = frappe.db.count("LMS Enrollment") all_membership = frappe.db.count("LMS Enrollment")
completed = frappe.db.count("LMS Enrollment", {"progress": ["like", "%100%"]}) completed = frappe.db.count("LMS Enrollment", {"progress": ["like", "%100%"]})

View File

@@ -1,12 +1,12 @@
import frappe import frappe
from frappe import _ from frappe import _
from lms.lms.utils import can_create_courses from lms.lms.utils import has_course_moderator_role, has_course_instructor_role
def get_context(context): def get_context(context):
context.no_cache = 1 context.no_cache = 1
if not can_create_courses(): if not has_course_moderator_role() or not has_course_instructor_role():
message = "You do not have permission to access this page." message = "You do not have permission to access this page."
if frappe.session.user == "Guest": if frappe.session.user == "Guest":
message = "Please login to access this page." message = "Please login to access this page."

View File

@@ -1,13 +1,13 @@
import frappe import frappe
from frappe.utils import cstr from frappe.utils import cstr
from frappe import _ from frappe import _
from lms.lms.utils import can_create_courses from lms.lms.utils import has_course_instructor_role, has_course_moderator_role
def get_context(context): def get_context(context):
context.no_cache = 1 context.no_cache = 1
if not can_create_courses(): if not has_course_moderator_role() or not has_course_instructor_role():
message = "You do not have permission to access this page." message = "You do not have permission to access this page."
if frappe.session.user == "Guest": if frappe.session.user == "Guest":
message = "Please login to access this page." message = "Please login to access this page."

View File

@@ -1,12 +1,12 @@
import frappe import frappe
from lms.lms.utils import can_create_courses, has_course_moderator_role from lms.lms.utils import has_course_instructor_role, has_course_moderator_role
from frappe import _ from frappe import _
def get_context(context): def get_context(context):
context.no_cache = 1 context.no_cache = 1
if not can_create_courses(): if not has_course_moderator_role() or not has_course_instructor_role():
message = "You do not have permission to access this page." message = "You do not have permission to access this page."
if frappe.session.user == "Guest": if frappe.session.user == "Guest":
message = "Please login to access this page." message = "Please login to access this page."

View File

@@ -552,7 +552,7 @@
{% for legend in legends %} {% for legend in legends %}
<div class="legend-item"> <div class="legend-item">
<div class="legend-color" style="background-color: {{ legend.color }}"></div> <div class="legend-color" style="background-color: {{ legend.color }}"></div>
<div class="legend-text">{{ legend.title }}</div> <div class="legend-text">{{ legend.label }}</div>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
@@ -574,6 +574,8 @@
<script> <script>
frappe.boot.single_types = [] frappe.boot.single_types = []
let courses = {{ course_list | json }}; let courses = {{ course_list | json }};
const legends = {{ legends | json }};
const allow_future = {{ batch_info.allow_future }}
</script> </script>
<link rel="stylesheet" href="https://uicdn.toast.com/calendar/latest/toastui-calendar.min.css" /> <link rel="stylesheet" href="https://uicdn.toast.com/calendar/latest/toastui-calendar.min.css" />

View File

@@ -698,8 +698,8 @@ const get_calendar_options = (element, calendar_id) => {
const create_events = (calendar, events, calendar_id) => { const create_events = (calendar, events, calendar_id) => {
let calendar_events = []; let calendar_events = [];
events.forEach((event, idx) => { events.forEach((event, idx) => {
let clr = get_background_color(event.reference_doctype);
calendar_events.push({ calendar_events.push({
id: `event${idx}`, id: `event${idx}`,
calendarId: calendar_id, calendarId: calendar_id,
@@ -707,7 +707,7 @@ const create_events = (calendar, events, calendar_id) => {
start: `${event.date}T${event.start_time}`, start: `${event.date}T${event.start_time}`,
end: `${event.date}T${event.end_time}`, end: `${event.date}T${event.end_time}`,
isAllday: event.start_time ? false : true, isAllday: event.start_time ? false : true,
borderColor: get_background_color(event.reference_doctype), borderColor: clr,
backgroundColor: "var(--fg-color)", backgroundColor: "var(--fg-color)",
customStyle: { customStyle: {
borderRadius: "var(--border-radius-md)", borderRadius: "var(--border-radius-md)",
@@ -724,10 +724,15 @@ const create_events = (calendar, events, calendar_id) => {
calendar.createEvents(calendar_events); calendar.createEvents(calendar_events);
}; };
const add_links_to_events = (calendar, events) => { const add_links_to_events = (calendar) => {
calendar.on("clickEvent", ({ event }) => { calendar.on("clickEvent", ({ event }) => {
const el = document.getElementById("clicked-event"); let event_date = event.start.d.d;
event_date = moment(event_date).format("YYYY-MM-DD");
let current_date = moment().format("YYYY-MM-DD");
if (allow_future || moment(event_date).isSameOrBefore(current_date)) {
window.open(event.raw.url, "_blank"); window.open(event.raw.url, "_blank");
}
}); });
}; };
@@ -764,10 +769,10 @@ const set_calendar_range = (calendar, events) => {
}; };
const get_background_color = (doctype) => { const get_background_color = (doctype) => {
if (doctype == "Course Lesson") return "var(--blue-400)"; const match = legends.filter((legend) => {
if (doctype == "LMS Quiz") return "var(--green-400)"; return legend.reference_doctype == doctype;
if (doctype == "LMS Assignment") return "var(--orange-400)"; });
if (doctype == "LMS Live Class") return "var(--purple-400)"; if (match.length) return match[0].color;
}; };
const email_to_students = () => { const email_to_students = () => {

View File

@@ -42,6 +42,7 @@ def get_context(context):
"currency", "currency",
"batch_details", "batch_details",
"published", "published",
"allow_future",
], ],
as_dict=True, as_dict=True,
) )
@@ -96,7 +97,7 @@ def get_context(context):
"parent": batch_name, "parent": batch_name,
}, },
) )
context.legends = get_legends() context.legends = get_legends(batch_name)
custom_tabs = frappe.get_hooks("lms_batch_tabs") custom_tabs = frappe.get_hooks("lms_batch_tabs")
@@ -261,22 +262,9 @@ def get_course_progress(batch_courses, student_details):
student_details.courses[course.course] = 0 student_details.courses[course.course] = 0
def get_legends(): def get_legends(batch):
return [ return frappe.get_all(
{ "LMS Timetable Legend",
"title": "Lesson", filters={"parenttype": "LMS Batch", "parent": batch},
"color": "var(--blue-400)", fields=["reference_doctype", "color", "label"],
}, )
{
"title": "Quiz",
"color": "var(--green-400)",
},
{
"title": "Assignment",
"color": "var(--orange-400)",
},
{
"title": "Live Class",
"color": "var(--purple-400)",
},
]

View File

@@ -23,7 +23,7 @@ def get_context(context):
redirect_to_courses_list() redirect_to_courses_list()
if course_name == "new-course": if course_name == "new-course":
if not can_create_courses(): if not can_create_courses(course_name):
message = "You do not have permission to access this page." message = "You do not have permission to access this page."
if frappe.session.user == "Guest": if frappe.session.user == "Guest":
message = "Please login to access this page." message = "Please login to access this page."

View File

@@ -15,7 +15,7 @@ def get_context(context):
except KeyError: except KeyError:
redirect_to_courses_list() redirect_to_courses_list()
if not can_create_courses(): if not can_create_courses(course_name):
message = "You do not have permission to access this page." message = "You do not have permission to access this page."
if frappe.session.user == "Guest": if frappe.session.user == "Guest":
message = "Please login to access this page." message = "Please login to access this page."

View File

@@ -1,13 +1,13 @@
import frappe import frappe
from frappe import _ from frappe import _
from lms.lms.utils import ( from lms.lms.utils import (
can_create_courses,
check_profile_restriction, check_profile_restriction,
get_restriction_details, get_restriction_details,
has_course_moderator_role, has_course_moderator_role,
get_courses_under_review, get_courses_under_review,
get_average_rating, get_average_rating,
check_multicurrency, check_multicurrency,
has_course_instructor_role,
) )
from lms.overrides.user import get_enrolled_courses, get_authored_courses from lms.overrides.user import get_enrolled_courses, get_authored_courses
@@ -21,7 +21,17 @@ def get_context(context):
context.created_courses = get_authored_courses(None, False) context.created_courses = get_authored_courses(None, False)
context.review_courses = get_courses_under_review() context.review_courses = get_courses_under_review()
context.restriction = check_profile_restriction() context.restriction = check_profile_restriction()
context.show_creators_section = can_create_courses()
portal_course_creation = frappe.db.get_single_value(
"LMS Settings", "portal_course_creation"
)
context.show_creators_section = (
True
if portal_course_creation == "Anyone"
or has_course_moderator_role()
or has_course_instructor_role()
else False
)
context.show_review_section = ( context.show_review_section = (
has_course_moderator_role() and frappe.session.user != "Guest" has_course_moderator_role() and frappe.session.user != "Guest"
) )

View File

@@ -10,7 +10,7 @@ def get_context(context):
if not frappe.db.exists("LMS Course", course_name): if not frappe.db.exists("LMS Course", course_name):
redirect_to_courses_list() redirect_to_courses_list()
if not can_create_courses(): if not can_create_courses(course_name):
message = "You do not have permission to access this page." message = "You do not have permission to access this page."
if frappe.session.user == "Guest": if frappe.session.user == "Guest":
message = "Please login to access this page." message = "Please login to access this page."