refactor: renamed app to lms

This commit is contained in:
Jannat Patel
2022-03-19 17:30:00 +05:30
parent 1b04615bff
commit c971d34d67
643 changed files with 227 additions and 235 deletions

0
lms/lms/__init__.py Normal file
View File

163
lms/lms/api.py Normal file
View File

@@ -0,0 +1,163 @@
"""API methods for the LMS.
"""
import frappe
@frappe.whitelist()
def autosave_section(section, code):
"""Saves the code edited in one of the sections.
"""
doc = frappe.get_doc(
doctype="Code Revision",
section=section,
code=code,
author=frappe.session.user)
doc.insert()
return {"name": doc.name}
@frappe.whitelist()
def submit_solution(exercise, code):
"""Submits a solution.
@exerecise: name of the exercise to submit
@code: solution to the exercise
"""
ex = frappe.get_doc("Exercise", exercise)
if not ex:
return
doc = ex.submit(code)
return {"name": doc.name, "creation": doc.creation}
@frappe.whitelist()
def save_current_lesson(course_name, lesson_name):
"""Saves the current lesson for a student/mentor.
"""
name = frappe.get_value(
doctype="LMS Batch Membership",
filters={
"course": course_name,
"member": frappe.session.user
},
fieldname="name")
if not name:
return
doc = frappe.get_doc("LMS Batch Membership", name)
doc.current_lesson = lesson_name
doc.save(ignore_permissions=True)
return {"current_lesson": doc.current_lesson}
@frappe.whitelist()
def join_cohort(course, cohort, subgroup, invite_code):
"""Creates a Cohort Join Request for given user.
"""
course_doc = frappe.get_doc("LMS Course", course)
cohort_doc = course_doc and course_doc.get_cohort(cohort)
subgroup_doc = cohort_doc and cohort_doc.get_subgroup(subgroup)
if not subgroup_doc or subgroup_doc.invite_code != invite_code:
return {
"ok": False,
"error": "Invalid join link"
}
data = {
"doctype": "Cohort Join Request",
"cohort": cohort_doc.name,
"subgroup": subgroup_doc.name,
"email": frappe.session.user,
"status": "Pending"
}
# Don't insert duplicate records
if frappe.db.exists(data):
return {"ok": True, "status": "record found"}
else:
doc = frappe.get_doc(data)
doc.insert(ignore_permissions=True)
return {"ok": True, "status": "record created"}
@frappe.whitelist()
def approve_cohort_join_request(join_request):
r = frappe.get_doc("Cohort Join Request", join_request)
sg = r and frappe.get_doc("Cohort Subgroup", r.subgroup)
if not sg or r.status not in ["Pending", "Accepted"]:
return {
"ok": False,
"error": "Invalid Join Request"
}
if not sg.is_manager(frappe.session.user) and "System Manager" not in frappe.get_roles():
return {
"ok": False,
"error": "Permission Deined"
}
r.status = "Accepted"
r.save(ignore_permissions=True)
return {"ok": True}
@frappe.whitelist()
def reject_cohort_join_request(join_request):
r = frappe.get_doc("Cohort Join Request", join_request)
sg = r and frappe.get_doc("Cohort Subgroup", r.subgroup)
if not sg or r.status not in ["Pending", "Rejected"]:
return {
"ok": False,
"error": "Invalid Join Request"
}
if not sg.is_manager(frappe.session.user) and "System Manager" not in frappe.get_roles():
return {
"ok": False,
"error": "Permission Deined"
}
r.status = "Rejected"
r.save(ignore_permissions=True)
return {"ok": True}
@frappe.whitelist()
def undo_reject_cohort_join_request(join_request):
r = frappe.get_doc("Cohort Join Request", join_request)
sg = r and frappe.get_doc("Cohort Subgroup", r.subgroup)
# keeping Pending as well to consider the case of duplicate requests
if not sg or r.status not in ["Pending", "Rejected"]:
return {
"ok": False,
"error": "Invalid Join Request"
}
if not sg.is_manager(frappe.session.user) and "System Manager" not in frappe.get_roles():
return {
"ok": False,
"error": "Permission Deined"
}
r.status = "Pending"
r.save(ignore_permissions=True)
return {"ok": True}
@frappe.whitelist()
def add_mentor_to_subgroup(subgroup, email):
try:
sg = frappe.get_doc("Cohort Subgroup", subgroup)
except frappe.DoesNotExistError:
return {
"ok": False,
"error": f"Invalid subgroup: {subgroup}"
}
if not sg.get_cohort().is_admin(frappe.session.user) and "System Manager" not in frappe.get_roles():
return {
"ok": False,
"error": "Permission Deined"
}
try:
user = frappe.get_doc("User", email)
except frappe.DoesNotExistError:
return {
"ok": False,
"error": f"Invalid user: {email}"
}
sg.add_mentor(email)
return {"ok": True}

View File

@@ -0,0 +1,30 @@
{
"based_on": "creation",
"chart_name": "Course Enrollments",
"chart_type": "Count",
"creation": "2021-09-30 12:23:09.414853",
"docstatus": 0,
"doctype": "Dashboard Chart",
"document_type": "LMS Batch Membership",
"dynamic_filters_json": "[]",
"filters_json": "[]",
"group_by_type": "Count",
"idx": 0,
"is_public": 1,
"is_standard": 1,
"last_synced_on": "2021-10-17 13:32:21.745498",
"modified": "2021-10-21 17:31:06.997133",
"modified_by": "Administrator",
"module": "LMS",
"name": "Course Enrollments",
"number_of_groups": 0,
"owner": "basawaraj@erpnext.com",
"source": "",
"time_interval": "Daily",
"timeseries": 1,
"timespan": "Last Quarter",
"type": "Line",
"use_report_chart": 0,
"value_based_on": "",
"y_axis": []
}

View File

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2021, Frappe and contributors
// For license information, please see license.txt
frappe.ui.form.on('Certification', {
// refresh: function(frm) {
// }
});

View File

@@ -0,0 +1,74 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "hash",
"creation": "2021-12-07 12:20:37.143096",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"certification_name",
"organization",
"description",
"column_break_4",
"expire",
"issue_date",
"expiration_date"
],
"fields": [
{
"fieldname": "certification_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Certification Name",
"reqd": 1
},
{
"fieldname": "organization",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Organization",
"reqd": 1
},
{
"fieldname": "issue_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Issue Date",
"reqd": 1
},
{
"fieldname": "description",
"fieldtype": "Small Text",
"label": "Description"
},
{
"depends_on": "eval: !doc.expire",
"fieldname": "expiration_date",
"fieldtype": "Data",
"label": "Expiration Date"
},
{
"default": "0",
"fieldname": "expire",
"fieldtype": "Check",
"label": "This certificate does no expire"
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-12-21 10:05:43.377876",
"modified_by": "Administrator",
"module": "LMS",
"name": "Certification",
"naming_rule": "Random",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC"
}

View File

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

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2021, Frappe and Contributors
# See license.txt
# import frappe
import unittest
class TestCertification(unittest.TestCase):
pass

View File

@@ -0,0 +1,35 @@
{
"actions": [],
"autoname": "hash",
"creation": "2021-07-27 16:25:02.903245",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"chapter"
],
"fields": [
{
"fieldname": "chapter",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Chapter",
"options": "Course Chapter",
"reqd": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-03-15 09:39:41.937565",
"modified_by": "Administrator",
"module": "LMS",
"name": "Chapter Reference",
"naming_rule": "Random",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2021, FOSS United and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class ChapterReference(Document):
pass

View File

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2021, FOSS United and contributors
// For license information, please see license.txt
frappe.ui.form.on('Cohort', {
// refresh: function(frm) {
// }
});

View File

@@ -0,0 +1,131 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "format:{course}/{slug}",
"creation": "2021-11-19 11:45:31.016097",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"course",
"title",
"slug",
"section_break_2",
"instructor",
"status",
"column_break_4",
"begin_date",
"end_date",
"duration",
"section_break_8",
"description",
"pages"
],
"fields": [
{
"fieldname": "description",
"fieldtype": "Markdown Editor",
"label": "Description"
},
{
"fieldname": "instructor",
"fieldtype": "Link",
"label": "Instructor",
"options": "User",
"reqd": 1
},
{
"fieldname": "status",
"fieldtype": "Select",
"label": "Status",
"options": "Upcoming\nLive\nCompleted\nCancelled",
"reqd": 1
},
{
"fieldname": "begin_date",
"fieldtype": "Date",
"label": "Begin Date"
},
{
"fieldname": "end_date",
"fieldtype": "Date",
"label": "End Date"
},
{
"fieldname": "duration",
"fieldtype": "Data",
"label": "Duration"
},
{
"fieldname": "slug",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Slug",
"unique": 1
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_8",
"fieldtype": "Section Break"
},
{
"fieldname": "section_break_2",
"fieldtype": "Section Break"
},
{
"fieldname": "title",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Title",
"reqd": 1
},
{
"fieldname": "course",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Course",
"options": "LMS Course",
"reqd": 1
},
{
"fieldname": "pages",
"fieldtype": "Table",
"label": "Pages",
"options": "Cohort Web Page"
}
],
"index_web_pages_for_search": 1,
"links": [
{
"group": "Links",
"link_doctype": "Cohort Subgroup",
"link_fieldname": "cohort"
}
],
"modified": "2021-12-16 14:44:25.406301",
"modified_by": "Administrator",
"module": "LMS",
"name": "Cohort",
"naming_rule": "Expression",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,85 @@
# Copyright (c) 2021, FOSS United and contributors
# For license information, please see license.txt
import frappe
from frappe.model.document import Document
class Cohort(Document):
def get_url(self):
return f"{frappe.utils.get_url()}/courses/{self.course}/cohorts/{self.slug}"
def get_subgroups(self, include_counts=False, sort_by=None):
names = frappe.get_all("Cohort Subgroup", filters={"cohort": self.name}, pluck="name")
subgroups = [frappe.get_cached_doc("Cohort Subgroup", name) for name in names]
subgroups = sorted(subgroups, key=lambda sg: sg.title)
if include_counts:
mentors = self._get_subgroup_counts("Cohort Mentor")
students = self._get_subgroup_counts("LMS Batch Membership")
join_requests = self._get_subgroup_counts("Cohort Join Request", status="Pending")
for s in subgroups:
s.num_mentors = mentors.get(s.name, 0)
s.num_students = students.get(s.name, 0)
s.num_join_requests = join_requests.get(s.name, 0)
if sort_by:
subgroups.sort(key=lambda sg: getattr(sg, sort_by), reverse=True)
return subgroups
def _get_subgroup_counts(self, doctype, **kw):
rows = frappe.get_all(doctype,
filters={"cohort": self.name, **kw},
fields=['subgroup', 'count(*) as count'],
group_by='subgroup')
return {row['subgroup']: row['count'] for row in rows}
def _get_count(self, doctype, **kw):
filters = {"cohort": self.name, **kw}
return frappe.db.count(doctype, filters=filters)
def get_page_template(self, slug, scope=None):
p = self.get_page(slug, scope=scope)
return p and p.get_template_html()
def get_page(self, slug, scope=None):
for p in self.pages:
if p.slug == slug and scope in [p.scope, None]:
return p
def get_pages(self, scope=None):
return [p for p in self.pages if scope in [p.scope, None]]
def get_stats(self):
return {
"subgroups": self._get_count("Cohort Subgroup"),
"mentors": self._get_count("Cohort Mentor"),
"students": self._get_count("LMS Batch Membership"),
"join_requests": self._get_count("Cohort Join Request", status="Pending"),
}
def get_subgroup(self, slug):
q = dict(cohort=self.name, slug=slug)
name = frappe.db.get_value("Cohort Subgroup", q, "name")
return name and frappe.get_doc("Cohort Subgroup", name)
def get_mentor(self, email):
q = dict(cohort=self.name, email=email)
name = frappe.db.get_value("Cohort Mentor", q, "name")
return name and frappe.get_doc("Cohort Mentor", name)
def is_mentor(self, email):
q = {
"doctype": "Cohort Mentor",
"cohort": self.name,
"email": email
}
return frappe.db.exists(q)
def is_admin(self, email):
q = {
"doctype": "Cohort Staff",
"cohort": self.name,
"email": email,
"role": "Admin"
}
return frappe.db.exists(q)

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2021, FOSS United and Contributors
# See license.txt
# import frappe
import unittest
class TestCohort(unittest.TestCase):
pass

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2021, FOSS United and contributors
// For license information, please see license.txt
frappe.ui.form.on('Cohort Join Request', {
// refresh: function(frm) {
// }
});

View File

@@ -0,0 +1,76 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2021-11-19 16:27:41.716509",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"cohort",
"email",
"column_break_3",
"subgroup",
"status"
],
"fields": [
{
"fieldname": "cohort",
"fieldtype": "Link",
"label": "Cohort",
"options": "Cohort",
"reqd": 1
},
{
"fieldname": "subgroup",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Subgroup",
"options": "Cohort Subgroup",
"reqd": 1
},
{
"fieldname": "email",
"fieldtype": "Link",
"in_list_view": 1,
"label": "E-Mail",
"options": "User",
"reqd": 1
},
{
"default": "Pending",
"fieldname": "status",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Status",
"options": "Pending\nAccepted\nRejected"
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-12-16 15:06:03.985221",
"modified_by": "Administrator",
"module": "LMS",
"name": "Cohort Join Request",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,51 @@
# Copyright (c) 2021, FOSS United and contributors
# For license information, please see license.txt
import frappe
from frappe.model.document import Document
class CohortJoinRequest(Document):
def on_update(self):
if self.status == "Accepted":
self.ensure_student()
def ensure_student(self):
# case 1 - user is already a member
q = {
"doctype": "LMS Batch Membership",
"cohort": self.cohort,
"subgroup": self.subgroup,
"member": self.email,
"member_type": "Student"
}
if frappe.db.exists(q):
return
# case 2 - user has signed up for this course, possibly not this cohort
cohort = frappe.get_doc("Cohort", self.cohort)
q = {
"doctype": "LMS Batch Membership",
"course": cohort.course,
"member": self.email,
"member_type": "Student"
}
name = frappe.db.exists(q)
if name:
doc = frappe.get_doc("LMS Batch Membership", name)
doc.cohort = self.cohort
doc.subgroup = self.subgroup
doc.save(ignore_permissions=True)
else:
# case 3 - user has not signed up for this course yet
data = {
"doctype": "LMS Batch Membership",
"course": cohort.course,
"cohort": self.cohort,
"subgroup": self.subgroup,
"member": self.email,
"member_type": "Student",
"role": "Member"
}
doc = frappe.get_doc(data)
doc.insert(ignore_permissions=True)

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2021, FOSS United and Contributors
# See license.txt
# import frappe
import unittest
class TestCohortJoinRequest(unittest.TestCase):
pass

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2021, FOSS United and contributors
// For license information, please see license.txt
frappe.ui.form.on('Cohort Mentor', {
// refresh: function(frm) {
// }
});

View File

@@ -0,0 +1,73 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2021-11-19 15:31:47.129156",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"cohort",
"email",
"subgroup",
"course"
],
"fields": [
{
"fieldname": "cohort",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Cohort",
"options": "Cohort",
"reqd": 1
},
{
"fieldname": "email",
"fieldtype": "Link",
"in_list_view": 1,
"label": "E-mail",
"options": "User",
"reqd": 1
},
{
"fieldname": "subgroup",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Primary Subgroup",
"options": "Cohort Subgroup",
"reqd": 1
},
{
"fetch_from": "cohort.course",
"fieldname": "course",
"fieldtype": "Link",
"label": "Course",
"options": "LMS Course",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-11-29 16:32:33.235281",
"modified_by": "Administrator",
"module": "LMS",
"name": "Cohort Mentor",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,12 @@
# Copyright (c) 2021, FOSS United and contributors
# For license information, please see license.txt
import frappe
from frappe.model.document import Document
class CohortMentor(Document):
def get_subgroup(self):
return frappe.get_doc("Cohort Subgroup", self.subgroup)
def get_user(self):
return frappe.get_doc("User", self.email)

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2021, FOSS United and Contributors
# See license.txt
# import frappe
import unittest
class TestCohortMentor(unittest.TestCase):
pass

View File

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2021, FOSS United and contributors
// For license information, please see license.txt
frappe.ui.form.on('Cohort Staff', {
// refresh: function(frm) {
// }
});

View File

@@ -0,0 +1,77 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2021-11-19 15:35:00.551949",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"cohort",
"course",
"column_break_3",
"email",
"role"
],
"fields": [
{
"fieldname": "cohort",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Cohort",
"options": "Cohort",
"reqd": 1
},
{
"fieldname": "email",
"fieldtype": "Link",
"in_list_view": 1,
"label": "User",
"options": "User",
"reqd": 1
},
{
"fieldname": "role",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Role",
"options": "Admin\nManager\nStaff",
"reqd": 1
},
{
"fetch_from": "cohort.course",
"fieldname": "course",
"fieldtype": "Link",
"label": "Course",
"options": "LMS Course",
"read_only": 1
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-12-16 15:16:04.042372",
"modified_by": "Administrator",
"module": "LMS",
"name": "Cohort Staff",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2021, FOSS United and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class CohortStaff(Document):
pass

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2021, FOSS United and Contributors
# See license.txt
# import frappe
import unittest
class TestCohortStaff(unittest.TestCase):
pass

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2021, FOSS United and contributors
// For license information, please see license.txt
frappe.ui.form.on('Cohort Subgroup', {
// refresh: function(frm) {
// }
});

View File

@@ -0,0 +1,104 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "format:{title} ({cohort})",
"creation": "2021-11-19 11:50:27.312434",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"cohort",
"slug",
"title",
"column_break_4",
"invite_code",
"course",
"section_break_7",
"description"
],
"fields": [
{
"fieldname": "cohort",
"fieldtype": "Link",
"in_list_view": 1,
"in_preview": 1,
"in_standard_filter": 1,
"label": "Cohort",
"options": "Cohort",
"reqd": 1
},
{
"fieldname": "title",
"fieldtype": "Data",
"in_list_view": 1,
"in_preview": 1,
"in_standard_filter": 1,
"label": "Title",
"reqd": 1
},
{
"fieldname": "description",
"fieldtype": "Markdown Editor",
"label": "Description"
},
{
"fieldname": "invite_code",
"fieldtype": "Data",
"label": "Invite Code",
"read_only": 1
},
{
"fieldname": "slug",
"fieldtype": "Data",
"label": "Slug",
"reqd": 1
},
{
"fetch_from": "cohort.course",
"fieldname": "course",
"fieldtype": "Link",
"label": "Course",
"options": "LMS Course",
"read_only": 1
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_7",
"fieldtype": "Section Break"
}
],
"index_web_pages_for_search": 1,
"links": [
{
"group": "Links",
"link_doctype": "Cohort Join Request",
"link_fieldname": "subgroup"
}
],
"modified": "2021-12-16 15:12:42.504883",
"modified_by": "Administrator",
"module": "LMS",
"name": "Cohort Subgroup",
"naming_rule": "Expression",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,95 @@
# Copyright (c) 2021, FOSS United and contributors
# For license information, please see license.txt
import frappe
from frappe.model.document import Document
from frappe.utils import random_string
class CohortSubgroup(Document):
def before_save(self):
if not self.invite_code:
self.invite_code = random_string(8)
def get_url(self):
cohort = frappe.get_doc("Cohort", self.cohort)
return f"{frappe.utils.get_url()}/courses/{self.course}/subgroups/{cohort.slug}/{self.slug}"
def get_invite_link(self):
cohort = frappe.get_doc("Cohort", self.cohort)
return f"{frappe.utils.get_url()}/courses/{self.course}/join/{cohort.slug}/{self.slug}/{self.invite_code}"
def has_student(self, email):
"""Check if given user is a student of this subgroup.
"""
q = {
"doctype": "LMS Batch Membership",
"subgroup": self.name,
"member": email
}
return frappe.db.exists(q)
def has_join_request(self, email):
"""Check if given user is a student of this subgroup.
"""
q = {
"doctype": "Cohort Join Request",
"subgroup": self.name,
"email": email
}
return frappe.db.exists(q)
def get_join_requests(self, status="Pending"):
q = {
"subgroup": self.name,
"status": status
}
return frappe.get_all("Cohort Join Request", filters=q, fields=["*"], order_by="creation desc")
def get_mentors(self):
emails = frappe.get_all("Cohort Mentor", filters={"subgroup": self.name}, fields=["email"], pluck='email')
return self._get_users(emails)
def get_students(self):
emails = frappe.get_all("LMS Batch Membership",
filters={"subgroup": self.name},
fields=["member"],
pluck='member',
page_length=1000)
return self._get_users(emails)
def _get_users(self, emails):
users = [frappe.get_cached_doc("User", email) for email in emails]
return sorted(users, key=lambda user: user.full_name)
def is_mentor(self, email):
q = {
"doctype": "Cohort Mentor",
"subgroup": self.name,
"email": email
}
return frappe.db.exists(q)
def is_manager(self, email):
"""Returns True if the given user is a manager of this subgroup.
Mentors of the subgroup, admins of the Cohort are considered as managers.
"""
return self.is_mentor(email) or self.get_cohort().is_admin(email)
def get_cohort(self):
return frappe.get_doc("Cohort", self.cohort)
def add_mentor(self, email):
d = {
"doctype": "Cohort Mentor",
"subgroup": self.name,
"cohort": self.cohort,
"email": email
}
if frappe.db.exists(d):
return
doc = frappe.get_doc(d)
doc.insert(ignore_permissions=True)
#def after_doctype_insert():
# frappe.db.add_unique("Cohort Subgroup", ("cohort", "slug"))

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2021, FOSS United and Contributors
# See license.txt
# import frappe
import unittest
class TestCohortSubgroup(unittest.TestCase):
pass

View File

@@ -0,0 +1,64 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2021-12-04 23:28:40.429867",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"slug",
"title",
"template",
"scope",
"required_role"
],
"fields": [
{
"fieldname": "title",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Title",
"reqd": 1
},
{
"fieldname": "template",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Template",
"options": "Web Template",
"reqd": 1
},
{
"default": "Cohort",
"fieldname": "scope",
"fieldtype": "Select",
"label": "Scope",
"options": "Cohort\nSubgroup"
},
{
"default": "Public",
"fieldname": "required_role",
"fieldtype": "Select",
"label": "Required Role",
"options": "Public\nStudent\nMentor\nAdmin"
},
{
"fieldname": "slug",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Slug",
"reqd": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-12-04 23:33:03.954128",
"modified_by": "Administrator",
"module": "LMS",
"name": "Cohort Web Page",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC"
}

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2021, Frappe and contributors
# For license information, please see license.txt
import frappe
from frappe.model.document import Document
class CohortWebPage(Document):
def get_template_html(self):
return frappe.get_doc("Web Template", self.template).template

View File

@@ -0,0 +1,14 @@
// Copyright (c) 2021, FOSS United and contributors
// For license information, please see license.txt
frappe.ui.form.on('Course Chapter', {
onload: function (frm) {
frm.set_query("lesson", "lessons", function () {
return {
filters: {
"chapter": frm.doc.name,
}
};
});
}
});

View File

@@ -0,0 +1,102 @@
{
"actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "format:{####} {title}",
"creation": "2021-05-03 05:49:08.383058",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"course",
"title",
"column_break_3",
"description",
"section_break_5",
"lessons"
],
"fields": [
{
"fieldname": "course",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Course",
"options": "LMS Course",
"reqd": 1
},
{
"fieldname": "title",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Title",
"reqd": 1
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"fieldname": "description",
"fieldtype": "Small Text",
"label": "Description"
},
{
"fieldname": "section_break_5",
"fieldtype": "Section Break"
},
{
"fieldname": "lessons",
"fieldtype": "Table",
"label": "Lessons",
"options": "Lesson Reference"
}
],
"index_web_pages_for_search": 1,
"links": [
{
"group": "Lessons",
"link_doctype": "Course Lesson",
"link_fieldname": "chapter"
}
],
"modified": "2022-03-14 17:57:00.707416",
"modified_by": "Administrator",
"module": "LMS",
"name": "Course Chapter",
"naming_rule": "Expression",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"email": 1,
"export": 1,
"if_owner": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "All",
"select": 1,
"share": 1,
"write": 1
}
],
"search_fields": "title",
"show_title_field_in_link": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "title",
"track_changes": 1
}

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2021, FOSS United and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class CourseChapter(Document):
pass

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2021, FOSS United and Contributors
# See license.txt
# import frappe
import unittest
class TestCourseChapter(unittest.TestCase):
pass

View File

@@ -0,0 +1,32 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2022-02-07 11:39:59.998762",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"instructor"
],
"fields": [
{
"fieldname": "instructor",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Instructor",
"options": "User"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-02-07 11:41:42.943250",
"modified_by": "Administrator",
"module": "LMS",
"name": "Course Instructor",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

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

View File

@@ -0,0 +1,104 @@
// Copyright (c) 2021, FOSS United and contributors
// For license information, please see license.txt
frappe.ui.form.on('Course Lesson', {
setup: function (frm) {
frm.trigger('setup_help');
},
setup_help(frm) {
frm.get_field('help').html(`
<p>You can add some more 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>
<div class="row font-weight-bold mb-3">
<div class="col-sm-8">
Content Type
</div>
<div class="col-sm-4">
Syntax
</div>
</div>
<div class="row mb-3">
<div class="col-sm-8">
Video
</div>
<div class="col-sm-4">
{{ Video("url_of_source") }}
</div>
</div>
<div class="row mb-3">
<div class="col-sm-8">
YouTube Video
</div>
<div class="col-sm-4">
{{ YouTubeVideo("unique_embed_id") }}
</div>
</div>
<div class="row mb-3">
<div class="col-sm-8">
Exercise
</div>
<div class="col-sm-4">
{{ Exercise("exercise_name") }}
</div>
</div>
<div class="row mb-3">
<div class="col-sm-8">
Quiz
</div>
<div class="col-sm-4">
{{ Quiz("lms_quiz_name") }}
</div>
</div>
<div class="row mb-3">
<div class="col-sm-8">
Assignment
</div>
<div class="col-sm-4">
{{ Assignment("id-filetype") }}
</div>
</div>
<hr>
<div class="row font-weight-bold mb-3">
<div class="col-sm-8">
Supported File Types for Assignment
</div>
<div class="col-sm-4">
Syntax
</div>
</div>
<div class="row mb-3">
<div class="col-sm-8">
.doc,.docx,.xml,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document
</div>
<div class="col-sm-4">
Document
</div>
</div>
<div class="row mb-3">
<div class="col-sm-8">
.pdf
</div>
<div class="col-sm-4">
PDF
</div>
</div>
<div class="row mb-3">
<div class="col-sm-8">
.png, .jpg, .jpeg
</div>
<div class="col-sm-4">
Image
</div>
</div>
`);
}
});

View File

@@ -0,0 +1,124 @@
{
"actions": [],
"allow_events_in_timeline": 1,
"allow_rename": 1,
"autoname": "format:{####} {title}",
"creation": "2021-05-03 06:21:12.995984",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"chapter",
"course",
"include_in_preview",
"column_break_4",
"title",
"index_label",
"section_break_6",
"body",
"help_section",
"help"
],
"fields": [
{
"fieldname": "chapter",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Course Chapter",
"options": "Course Chapter",
"reqd": 1
},
{
"default": "0",
"fieldname": "include_in_preview",
"fieldtype": "Check",
"label": "Include In Preview"
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"fieldname": "title",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Title",
"reqd": 1
},
{
"fieldname": "index_label",
"fieldtype": "Data",
"label": "Index Label",
"read_only": 1
},
{
"fieldname": "section_break_6",
"fieldtype": "Section Break"
},
{
"fieldname": "body",
"fieldtype": "Markdown Editor",
"ignore_xss_filter": 1,
"label": "Body",
"reqd": 1
},
{
"fieldname": "help_section",
"fieldtype": "Section Break",
"label": "Help"
},
{
"fieldname": "help",
"fieldtype": "HTML"
},
{
"fetch_from": "chapter.course",
"fieldname": "course",
"fieldtype": "Link",
"hidden": 1,
"label": "Course",
"options": "LMS Course",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2022-03-14 18:56:31.969801",
"modified_by": "Administrator",
"module": "LMS",
"name": "Course Lesson",
"naming_rule": "Expression",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"select": 1,
"share": 1,
"write": 1
},
{
"create": 1,
"email": 1,
"export": 1,
"if_owner": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "All",
"select": 1,
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -0,0 +1,114 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021, FOSS United and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from ...md import find_macros
from lms.lms.utils import get_course_progress, get_lesson_url
class CourseLesson(Document):
def validate(self):
self.check_and_create_folder()
def on_update(self):
dynamic_documents = ["Exercise", "Quiz"]
for section in dynamic_documents:
self.update_lesson_name_in_document(section)
def update_lesson_name_in_document(self, section):
doctype_map= {
"Exercise": "Exercise",
"Quiz": "LMS Quiz"
}
macros = find_macros(self.body)
documents = [value for name, value in macros if name == section]
index = 1
for name in documents:
e = frappe.get_doc(doctype_map[section], name)
e.lesson = self.name
e.index_ = index
e.save()
index += 1
self.update_orphan_documents(doctype_map[section], documents)
def update_orphan_documents(self, doctype, documents):
"""Updates the documents that were previously part of this lesson,
but not any more.
"""
linked_documents = {row['name'] for row in frappe.get_all(doctype, {"lesson": self.name})}
active_documents = set(documents)
orphan_documents = linked_documents - active_documents
for name in orphan_documents:
ex = frappe.get_doc(doctype, name)
ex.lesson = None
ex.index_ = 0
ex.index_label = ""
ex.save()
def check_and_create_folder(self):
course = frappe.db.get_value("Chapter", self.chapter, "course")
args = {
"doctype": "File",
"is_folder": True,
"file_name": f"{self.name} {course}"
}
if not frappe.db.exists(args):
folder = frappe.get_doc(args)
folder.save(ignore_permissions=True)
def get_exercises(self):
if not self.body:
return []
macros = find_macros(self.body)
exercises = [value for name, value in macros if name == "Exercise"]
return [frappe.get_doc("Exercise", name) for name in exercises]
def get_progress(self):
return frappe.db.get_value("LMS Course Progress", {"lesson": self.name, "owner": frappe.session.user}, "status")
def get_slugified_class(self):
if self.get_progress():
return ("").join([ s for s in self.get_progress().lower().split() ])
return
@frappe.whitelist()
def save_progress(lesson, course, status):
membership = frappe.db.exists("LMS Batch Membership",
{
"member": frappe.session.user,
"course": course
})
if not membership:
return
if frappe.db.exists("LMS Course Progress",
{
"lesson": lesson,
"owner": frappe.session.user,
"course": course
}):
doc = frappe.get_doc("LMS Course Progress",
{
"lesson": lesson,
"owner": frappe.session.user,
"course": course
})
doc.status = status
doc.save(ignore_permissions=True)
else:
frappe.get_doc({
"doctype": "LMS Course Progress",
"lesson": lesson,
"status": status,
}).save(ignore_permissions=True)
progress = get_course_progress(course)
frappe.db.set_value("LMS Batch Membership", membership, "progress", progress)
return progress
@frappe.whitelist()
def get_lesson_info(chapter):
return frappe.db.get_value("Course Chapter", chapter, "course")

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2021, FOSS United and Contributors
# See license.txt
# import frappe
import unittest
class TestCourseLesson(unittest.TestCase):
pass

View File

@@ -0,0 +1,87 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "hash",
"creation": "2021-12-07 12:15:46.078717",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"institution_name",
"location",
"degree_type",
"major",
"column_break_5",
"grade_type",
"grade",
"start_date",
"end_date"
],
"fields": [
{
"fieldname": "institution_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Institution Name",
"reqd": 1
},
{
"fieldname": "location",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Location",
"reqd": 1
},
{
"fieldname": "degree_type",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Degree Type",
"reqd": 1
},
{
"fieldname": "grade_type",
"fieldtype": "Select",
"label": "Grade Type",
"options": "Percentage (e.g. 70%)\nPoint of Score (e.g. 70)\nLetter Grade (e.g. A, B-)\nUK Grading (e.g. 1st, 2:2)\nFrench (e.g. Distinction)\nCGPA/4"
},
{
"fieldname": "grade",
"fieldtype": "Data",
"label": "Grade"
},
{
"fieldname": "major",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Field of Major/Study",
"reqd": 1
},
{
"fieldname": "start_date",
"fieldtype": "Date",
"label": "Start Date"
},
{
"fieldname": "end_date",
"fieldtype": "Date",
"label": "End Date (or expected)"
},
{
"fieldname": "column_break_5",
"fieldtype": "Column Break"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-12-21 09:58:42.343823",
"modified_by": "Administrator",
"module": "LMS",
"name": "Education Detail",
"naming_rule": "Random",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC"
}

View File

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

View File

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2021, FOSS United and contributors
// For license information, please see license.txt
frappe.ui.form.on('Exercise', {
// refresh: function(frm) {
// }
});

View File

@@ -0,0 +1,123 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2021-05-19 17:43:39.923430",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"title",
"description",
"code",
"answer",
"column_break_4",
"course",
"hints",
"tests",
"image",
"lesson",
"index_",
"index_label"
],
"fields": [
{
"fieldname": "title",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Title"
},
{
"fieldname": "course",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Course",
"options": "LMS Course"
},
{
"columns": 4,
"fieldname": "description",
"fieldtype": "Small Text",
"in_list_view": 1,
"label": "Description"
},
{
"columns": 4,
"fieldname": "answer",
"fieldtype": "Code",
"label": "Answer"
},
{
"fieldname": "tests",
"fieldtype": "Code",
"label": "Tests"
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"columns": 4,
"fieldname": "hints",
"fieldtype": "Small Text",
"label": "Hints"
},
{
"columns": 4,
"fieldname": "code",
"fieldtype": "Code",
"label": "Code"
},
{
"fieldname": "image",
"fieldtype": "Code",
"label": "Image",
"read_only": 1
},
{
"fieldname": "lesson",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Lesson",
"options": "Course Lesson"
},
{
"fieldname": "index_",
"fieldtype": "Int",
"label": "Index",
"read_only": 1
},
{
"fieldname": "index_label",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Index Label",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-09-29 15:27:55.585874",
"modified_by": "Administrator",
"module": "LMS",
"name": "Exercise",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"search_fields": "title",
"sort_field": "index_label",
"sort_order": "ASC",
"title_field": "title",
"track_changes": 1
}

View File

@@ -0,0 +1,53 @@
# Copyright (c) 2021, FOSS United and contributors
# For license information, please see license.txt
import frappe
from frappe.model.document import Document
from lms.lms.utils import get_membership
class Exercise(Document):
def get_user_submission(self):
"""Returns the latest submission for this user.
"""
user = frappe.session.user
if not user or user == "Guest":
return
result = frappe.get_all('Exercise Submission',
fields="*",
filters={
"owner": user,
"exercise": self.name
},
order_by="creation desc",
page_length=1)
if result:
return result[0]
def submit(self, code):
"""Submits the given code as solution to exercise.
"""
user = frappe.session.user
if not user or user == "Guest":
return
old_submission = self.get_user_submission()
if old_submission and old_submission.solution == code:
return old_submission
member = get_membership(self.course, frappe.session.user)
doc = frappe.get_doc(
doctype="Exercise Submission",
exercise=self.name,
exercise_title=self.title,
course=self.course,
lesson=self.lesson,
batch=member.batch,
solution=code,
member=member.name)
doc.insert(ignore_permissions=True)
return doc

View File

@@ -0,0 +1,56 @@
# Copyright (c) 2021, FOSS United and Contributors
# See license.txt
import frappe
import unittest
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`')
frappe.db.sql('delete from `tabLMS Course`')
def new_exercise(self):
course = frappe.get_doc({
"doctype": "LMS Course",
"name": "test-course",
"title": "Test Course",
"short_introduction": "Test Course",
"description": "Test Course"
})
course.insert()
member = frappe.get_doc({
"doctype": "LMS Batch Membership",
"course": course.name,
"member": frappe.session.user
})
member.insert()
e = frappe.get_doc({
"doctype": "Exercise",
"name": "test-problem",
"course": course.name,
"title": "Test Problem",
"description": "draw a circle",
"code": "# draw a single cicle",
"answer": (
"# draw a single circle\n" +
"circle(100, 100, 50)")
})
e.insert()
return e
def test_exercise(self):
e = self.new_exercise()
assert e.get_user_submission() is None
def test_exercise_submission(self):
e = self.new_exercise()
submission = e.submit("circle(100, 100, 50)")
assert submission is not None
assert submission.exercise == e.name
assert submission.course == e.course
user_submission = e.get_user_submission()
assert user_submission is not None
assert user_submission.name == submission.name

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2021, Frappe and contributors
// For license information, please see license.txt
frappe.ui.form.on('Exercise Latest Submission', {
// refresh: function(frm) {
// }
});

View File

@@ -0,0 +1,166 @@
{
"actions": [],
"creation": "2021-12-08 17:56:26.049675",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"exercise",
"status",
"batch",
"column_break_4",
"exercise_title",
"course",
"lesson",
"section_break_8",
"solution",
"image",
"test_results",
"comments",
"latest_submission",
"member",
"member_email",
"member_cohort",
"member_subgroup"
],
"fields": [
{
"fieldname": "exercise",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Exercise",
"options": "Exercise",
"search_index": 1
},
{
"fieldname": "status",
"fieldtype": "Select",
"label": "Status",
"options": "Correct\nIncorrect"
},
{
"fieldname": "batch",
"fieldtype": "Link",
"label": "Batch",
"options": "LMS Batch"
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"fetch_from": "exercise.title",
"fieldname": "exercise_title",
"fieldtype": "Data",
"label": "Exercise Title",
"read_only": 1
},
{
"fetch_from": "exercise.course",
"fieldname": "course",
"fieldtype": "Link",
"in_standard_filter": 1,
"label": "Course",
"options": "LMS Course",
"read_only": 1
},
{
"fetch_from": "exercise.lesson",
"fieldname": "lesson",
"fieldtype": "Link",
"label": "Lesson",
"options": "Course Lesson"
},
{
"fieldname": "section_break_8",
"fieldtype": "Section Break"
},
{
"fetch_from": "latest_submission.solution",
"fieldname": "solution",
"fieldtype": "Code",
"label": "Solution"
},
{
"fetch_from": "latest_submission.image",
"fieldname": "image",
"fieldtype": "Code",
"label": "Image",
"read_only": 1
},
{
"fetch_from": "latest_submission.test_results",
"fieldname": "test_results",
"fieldtype": "Small Text",
"label": "Test Results"
},
{
"fieldname": "comments",
"fieldtype": "Small Text",
"label": "Comments"
},
{
"fieldname": "latest_submission",
"fieldtype": "Link",
"label": "Latest Submission",
"options": "Exercise Submission"
},
{
"fieldname": "member",
"fieldtype": "Link",
"label": "Member",
"options": "LMS Batch Membership"
},
{
"fetch_from": "member.member",
"fieldname": "member_email",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Member Email",
"options": "User",
"search_index": 1
},
{
"fetch_from": "member.cohort",
"fieldname": "member_cohort",
"fieldtype": "Link",
"label": "Member Cohort",
"options": "Cohort",
"search_index": 1
},
{
"fetch_from": "member.subgroup",
"fieldname": "member_subgroup",
"fieldtype": "Link",
"label": "Member Subgroup",
"options": "Cohort Subgroup",
"search_index": 1
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-12-08 22:58:46.312861",
"modified_by": "Administrator",
"module": "LMS",
"name": "Exercise Latest Submission",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

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

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2021, Frappe and Contributors
# See license.txt
# import frappe
import unittest
class TestExerciseLatestSubmission(unittest.TestCase):
pass

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2021, FOSS United and contributors
// For license information, please see license.txt
frappe.ui.form.on('Exercise Submission', {
// refresh: function(frm) {
// }
});

View File

@@ -0,0 +1,126 @@
{
"actions": [],
"creation": "2021-05-19 11:41:18.108316",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"exercise",
"status",
"batch",
"column_break_4",
"exercise_title",
"course",
"lesson",
"section_break_8",
"solution",
"image",
"test_results",
"comments",
"member"
],
"fields": [
{
"fieldname": "exercise",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Exercise",
"options": "Exercise"
},
{
"fetch_from": "exercise.title",
"fieldname": "exercise_title",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Exercise Title",
"read_only": 1
},
{
"fetch_from": "exercise.course",
"fieldname": "course",
"fieldtype": "Link",
"label": "Course",
"options": "LMS Course",
"read_only": 1
},
{
"fieldname": "batch",
"fieldtype": "Link",
"label": "Batch",
"options": "LMS Batch"
},
{
"fetch_from": "exercise.lesson",
"fieldname": "lesson",
"fieldtype": "Link",
"label": "Lesson",
"options": "Course Lesson"
},
{
"fieldname": "image",
"fieldtype": "Code",
"label": "Image",
"read_only": 1
},
{
"fieldname": "status",
"fieldtype": "Select",
"label": "Status",
"options": "Correct\nIncorrect"
},
{
"fieldname": "test_results",
"fieldtype": "Small Text",
"label": "Test Results"
},
{
"fieldname": "comments",
"fieldtype": "Small Text",
"label": "Comments"
},
{
"fieldname": "solution",
"fieldtype": "Code",
"in_list_view": 1,
"label": "Solution"
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_8",
"fieldtype": "Section Break"
},
{
"fieldname": "member",
"fieldtype": "Link",
"label": "Member",
"options": "LMS Batch Membership"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-12-08 22:25:05.809376",
"modified_by": "Administrator",
"module": "LMS",
"name": "Exercise Submission",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,24 @@
# Copyright (c) 2021, FOSS United and contributors
# For license information, please see license.txt
import frappe
from frappe.model.document import Document
class ExerciseSubmission(Document):
def on_update(self):
self.update_latest_submission()
def update_latest_submission(self):
names = frappe.get_all("Exercise Latest Submission", {"exercise": self.exercise, "member": self.member})
if names:
doc = frappe.get_doc("Exercise Latest Submission", names[0])
doc.latest_submission = self.name
doc.save(ignore_permissions=True)
else:
doc = frappe.get_doc({
"doctype": "Exercise Latest Submission",
"exercise": self.exercise,
"member": self.member,
"latest_submission": self.name
})
doc.insert(ignore_permissions=True)

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2021, FOSS United and Contributors
# See license.txt
# import frappe
import unittest
class TestExerciseSubmission(unittest.TestCase):
pass

View File

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2021, Frappe and contributors
// For license information, please see license.txt
frappe.ui.form.on('Function', {
// refresh: function(frm) {
// }
});

View File

@@ -0,0 +1,54 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "field:function",
"creation": "2021-12-14 14:02:35.435916",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"function"
],
"fields": [
{
"fieldname": "function",
"fieldtype": "Data",
"label": "Function",
"unique": 1
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-12-21 09:34:35.018280",
"modified_by": "Administrator",
"module": "LMS",
"name": "Function",
"naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "All",
"select": 1,
"share": 1
}
],
"sort_field": "modified",
"sort_order": "DESC"
}

View File

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

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2021, Frappe and Contributors
# See license.txt
# import frappe
import unittest
class TestFunction(unittest.TestCase):
pass

View File

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2021, Frappe and contributors
// For license information, please see license.txt
frappe.ui.form.on('Industry', {
// refresh: function(frm) {
// }
});

View File

@@ -0,0 +1,54 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "field:industry",
"creation": "2021-12-14 14:37:47.183595",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"industry"
],
"fields": [
{
"fieldname": "industry",
"fieldtype": "Data",
"label": "Industry",
"unique": 1
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-12-21 09:35:20.443192",
"modified_by": "Administrator",
"module": "LMS",
"name": "Industry",
"naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "All",
"select": 1,
"share": 1
}
],
"sort_field": "modified",
"sort_order": "DESC"
}

View File

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

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2021, Frappe and Contributors
# See license.txt
# import frappe
import unittest
class TestIndustry(unittest.TestCase):
pass

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2021, FOSS United and contributors
// For license information, please see license.txt
frappe.ui.form.on('Invite Request', {
// refresh: function(frm) {
// }
});

View File

@@ -0,0 +1,88 @@
{
"actions": [],
"creation": "2021-04-29 16:29:56.857914",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"invite_email",
"signup_email",
"column_break_4",
"status",
"full_name",
"username",
"invite_code"
],
"fields": [
{
"allow_in_quick_entry": 1,
"fieldname": "invite_email",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Invite Email",
"options": "Email",
"unique": 1
},
{
"fieldname": "full_name",
"fieldtype": "Data",
"label": "Full Name"
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"fieldname": "signup_email",
"fieldtype": "Data",
"label": "Signup Email",
"options": "Email"
},
{
"fieldname": "username",
"fieldtype": "Data",
"label": "Username"
},
{
"fieldname": "invite_code",
"fieldtype": "Data",
"label": "Invite Code"
},
{
"default": "Pending",
"fieldname": "status",
"fieldtype": "Select",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Status",
"options": "Pending\nApproved\nRejected\nRegistered"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-05-03 09:22:20.954921",
"modified_by": "Administrator",
"module": "LMS",
"name": "Invite Request",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"search_fields": "invite_email, signup_email",
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "invite_email",
"track_changes": 1
}

View File

@@ -0,0 +1,90 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021, FOSS United and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.model.document import Document
import json
from frappe.utils.password import get_decrypted_password
class InviteRequest(Document):
def on_update(self):
if self.has_value_changed("status") and self.status == "Approved":
self.send_email()
def create_user(self, password):
full_name_split = self.full_name.split(" ")
user = frappe.get_doc({
"doctype": "User",
"email": self.signup_email,
"first_name": full_name_split[0],
"last_name": full_name_split[1] if len(full_name_split) > 1 else "",
"username": self.username,
"send_welcome_email": 0,
"user_type": "Website User",
"new_password": password
})
user.save(ignore_permissions=True)
return user
def send_email(self):
site_name = "Mon.School"
subject = _("Welcome to {0}!").format(site_name)
args = {
"full_name": self.full_name,
"signup_form_link": "/new-sign-up?invite_code={0}".format(self.name),
"site_name": site_name,
"site_url": frappe.utils.get_url()
}
frappe.sendmail(
recipients=self.invite_email,
subject=subject,
header=[subject, "green"],
template = "lms_invite_request_approved",
args=args,
now=True)
@frappe.whitelist(allow_guest=True)
def create_invite_request(invite_email):
if not frappe.utils.validate_email_address(invite_email):
return "invalid email"
if frappe.db.exists("User", invite_email):
return "user"
if frappe.db.exists("Invite Request", {"invite_email": invite_email}):
return "invite"
frappe.get_doc({
"doctype": "Invite Request",
"invite_email": invite_email,
"status": "Approved"
}).save(ignore_permissions=True)
return "OK"
@frappe.whitelist(allow_guest=True)
def update_invite(data):
data = frappe._dict(json.loads(data)) if type(data) == str else frappe._dict(data)
try:
doc = frappe.get_doc("Invite Request", data.invite_code)
except frappe.DoesNotExistError:
frappe.throw(_("Invalid Invite Code."))
doc.signup_email = data.signup_email
doc.username = data.username
doc.full_name = data.full_name
doc.invite_code = data.invite_code
doc.save(ignore_permissions=True)
user = doc.create_user(data.password)
if user:
doc.status = "Registered"
doc.save(ignore_permissions=True)
return "OK"

View File

@@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021, FOSS United and Contributors
# See license.txt
from __future__ import unicode_literals
from lms.lms.doctype.invite_request.invite_request import create_invite_request, update_invite
import frappe
import unittest
class TestInviteRequest(unittest.TestCase):
@classmethod
def setUpClass(self):
create_invite_request("test_invite@example.com")
def test_create_invite_request(self):
if frappe.db.exists("Invite Request", {"invite_email": "test_invite@example.com"}):
invite = frappe.db.get_value("Invite Request",
filters={"invite_email": "test_invite@example.com"},
fieldname=["invite_email", "status", "signup_email"],
as_dict=True)
self.assertEqual(invite.status, "Approved")
self.assertEqual(invite.signup_email, None)
def test_create_invite_request_update(self):
if frappe.db.exists("Invite Request", {"invite_email": "test_invite@example.com"}):
data = {
"signup_email": "test_invite@example.com",
"username": "test_invite",
"full_name": "Test Invite",
"password": "Test@invite",
"invite_code": frappe.db.get_value("Invite Request", {"invite_email": "test_invite@example.com"}, "name")
}
update_invite(data)
invite = frappe.db.get_value("Invite Request",
filters={"invite_email": "test_invite@example.com"},
fieldname=["invite_email", "status", "signup_email", "full_name", "username", "invite_code", "name"],
as_dict=True)
self.assertEqual(invite.signup_email, "test_invite@example.com")
self.assertEqual(invite.full_name, "Test Invite")
self.assertEqual(invite.username, "test_invite")
self.assertEqual(invite.invite_code, invite.name)
self.assertEqual(invite.status, "Registered")
user = frappe.db.get_value("User", "test_invite@example.com",
fieldname=["first_name", "username", "send_welcome_email", "user_type"],
as_dict=True)
self.assertTrue(user)
self.assertEqual(user.first_name, invite.full_name.split(" ")[0])
self.assertEqual(user.username, invite.username)
self.assertEqual(user.send_welcome_email, 0)
self.assertEqual(user.user_type, "Website User")
@classmethod
def tearDownClass(self):
if frappe.db.exists("User", "test_invite@example.com"):
frappe.delete_doc("User", "test_invite@example.com")
invite_request = frappe.db.exists("Invite Request", {"invite_email": "test_invite@example.com"})
if invite_request:
frappe.delete_doc("Invite Request", invite_request)

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2021, Frappe and contributors
// For license information, please see license.txt
frappe.ui.form.on('Lesson Assignment', {
// refresh: function(frm) {
// }
});

View File

@@ -0,0 +1,70 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2021-12-21 16:15:22.651658",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"lesson",
"user",
"column_break_3",
"assignment",
"id"
],
"fields": [
{
"fieldname": "lesson",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Lesson",
"options": "Course Lesson"
},
{
"fieldname": "user",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "User",
"options": "User"
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"fieldname": "id",
"fieldtype": "Data",
"label": "ID"
},
{
"fieldname": "assignment",
"fieldtype": "Attach",
"label": "Assignment"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-12-22 11:17:08.390615",
"modified_by": "Administrator",
"module": "LMS",
"name": "Lesson Assignment",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC"
}

View File

@@ -0,0 +1,43 @@
# Copyright (c) 2021, Frappe and contributors
# For license information, please see license.txt
import frappe
from frappe.model.document import Document
from frappe.handler import upload_file
class LessonAssignment(Document):
pass
@frappe.whitelist()
def upload_assignment(assignment, lesson, identifier):
args = {
"doctype": "Lesson Assignment",
"lesson": lesson,
"user": frappe.session.user,
"id": identifier
}
if frappe.db.exists(args):
del args["doctype"]
frappe.db.set_value("Lesson Assignment", args, "assignment", assignment)
else:
args.update({"assignment": assignment})
lesson_work = frappe.get_doc(args)
lesson_work.save(ignore_permissions=True)
@frappe.whitelist()
def get_assignment(lesson):
assignments = frappe.get_all("Lesson Assignment",
{
"lesson": lesson,
"user": frappe.session.user
},
["lesson", "user", "id", "assignment"])
if len(assignments):
for assignment in assignments:
assignment.file_name = frappe.db.get_value("File", {"file_url": assignment.assignment}, "file_name")
return assignments

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2021, Frappe and Contributors
# See license.txt
# import frappe
import unittest
class TestLessonAssignment(unittest.TestCase):
pass

View File

@@ -0,0 +1,35 @@
{
"actions": [],
"autoname": "hash",
"creation": "2021-07-27 16:25:48.269536",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"lesson"
],
"fields": [
{
"fieldname": "lesson",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Lesson",
"options": "Course Lesson",
"reqd": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-03-15 09:39:29.495991",
"modified_by": "Administrator",
"module": "LMS",
"name": "Lesson Reference",
"naming_rule": "Random",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2021, FOSS United and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class LessonReference(Document):
pass

View File

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2021, FOSS United and contributors
// For license information, please see license.txt
frappe.ui.form.on('LMS Batch', {
// refresh: function(frm) {
// }
});

View File

@@ -0,0 +1,145 @@
{
"actions": [],
"creation": "2021-03-18 19:37:34.614796",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"course",
"start_date",
"start_time",
"column_break_3",
"title",
"sessions_on",
"end_time",
"section_break_5",
"description",
"section_break_7",
"visibility",
"membership",
"column_break_9",
"status",
"stage"
],
"fields": [
{
"fieldname": "course",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Course",
"options": "LMS Course"
},
{
"fieldname": "title",
"fieldtype": "Data",
"label": "Title"
},
{
"fieldname": "description",
"fieldtype": "Markdown Editor",
"label": "Description"
},
{
"default": "Public",
"fieldname": "visibility",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Visibility",
"options": "Public\nUnlisted\nPrivate"
},
{
"fieldname": "membership",
"fieldtype": "Select",
"label": "Membership",
"options": "\nOpen\nRestricted\nInvite Only\nClosed"
},
{
"default": "Active",
"fieldname": "status",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Status",
"options": "Active\nInactive"
},
{
"default": "Ready",
"fieldname": "stage",
"fieldtype": "Select",
"label": "Stage",
"options": "Ready\nIn Progress\nCompleted\nCancelled"
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_5",
"fieldtype": "Section Break",
"label": "Batch Description"
},
{
"fieldname": "column_break_9",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_7",
"fieldtype": "Section Break",
"label": "Batch Settings"
},
{
"fieldname": "start_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Start Date"
},
{
"fieldname": "start_time",
"fieldtype": "Time",
"in_list_view": 1,
"label": "Start Time"
},
{
"fieldname": "sessions_on",
"fieldtype": "Data",
"label": "Sessions On Days"
},
{
"fieldname": "end_time",
"fieldtype": "Time",
"in_list_view": 1,
"label": "End Time"
}
],
"index_web_pages_for_search": 1,
"links": [
{
"group": "Members",
"link_doctype": "LMS Batch Membership",
"link_fieldname": "batch"
}
],
"modified": "2021-06-16 10:51:05.403726",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Batch",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,100 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021, FOSS United and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from frappe import _
from lms.lms.doctype.lms_batch_membership.lms_batch_membership import create_membership
from lms.query import find, find_all
from lms.lms.utils import is_mentor
class LMSBatch(Document):
def validate(self):
self.validate_if_mentor()
def validate_if_mentor(self):
if not is_mentor(self.course, frappe.session.user):
frappe.throw(_("You are not a mentor of the course {0}").format(course.title))
def after_insert(self):
create_membership(batch=self.name, course=self.course, member_type="Mentor")
def is_member(self, email, member_type=None):
"""Checks if a person is part of a batch.
If member_type is specified, checks if the person is a Student/Mentor.
"""
filters = {
"batch": self.name,
"member": email
}
if member_type:
filters['member_type'] = member_type
return frappe.db.exists("LMS Batch Membership", filters)
def get_membership(self, email):
"""Returns the membership document of given user.
"""
name = frappe.get_value(
doctype="LMS Batch Membership",
filters={
"batch": self.name,
"member": email
},
fieldname="name")
return frappe.get_doc("LMS Batch Membership", name)
def get_current_lesson(self, user):
"""Returns the name of the current lesson for the given user.
"""
membership = self.get_membership(user)
return membership and membership.current_lesson
@frappe.whitelist()
def save_message(message, batch):
doc = frappe.get_doc({
"doctype": "LMS Message",
"batch": batch,
"author": frappe.session.user,
"message": message
})
doc.save(ignore_permissions=True)
def switch_batch(course_name, email, batch_name):
"""Switches the user from the current batch of the course to a new batch.
"""
membership = frappe.get_last_doc(
"LMS Batch Membership",
filters={"course": course_name, "member": email})
batch = frappe.get_doc("LMS Batch", batch_name)
if not batch:
raise ValueError(f"Invalid Batch: {batch_name}")
if batch.course != course_name:
raise ValueError("Can not switch batches across courses")
if batch.is_member(email):
print(f"{email} is already a member of {batch.title}")
return
old_batch = frappe.get_doc("LMS Batch", membership.batch)
print("updating membership", membership.name)
membership.batch = batch_name
membership.save()
# update exercise submissions
filters = {
"owner": email,
"batch": old_batch.name
}
for name in frappe.db.get_all("Exercise Submission", filters=filters, pluck='name'):
doc = frappe.get_doc("Exercise Submission", name)
print("updating exercise submission", name)
doc.batch = batch_name
doc.save()

View File

@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021, FOSS United and Contributors
# See license.txt
from __future__ import unicode_literals
# import frappe
import unittest
class TestLMSBatch(unittest.TestCase):
pass

Some files were not shown because too many files have changed in this diff Show More