Merge pull request #422 from pateljannat/semgrep

This commit is contained in:
Jannat Patel
2022-11-04 13:54:08 +05:30
committed by GitHub
215 changed files with 17049 additions and 12825 deletions

View File

@@ -9,7 +9,7 @@ root = true
end_of_line = lf end_of_line = lf
insert_final_newline = true insert_final_newline = true
charset = utf-8 charset = utf-8
indent_style = space indent_style = tab
trim_trailing_whitespace = true trim_trailing_whitespace = true
# Python # Python

37
.flake8 Normal file
View File

@@ -0,0 +1,37 @@
[flake8]
ignore =
E121,
E126,
E127,
E128,
E203,
E225,
E226,
E231,
E241,
E251,
E261,
E265,
E302,
E303,
E305,
E402,
E501,
E741,
W291,
W292,
W293,
W391,
W503,
W504,
F403,
B007,
B950,
W191,
E124, # closing bracket, irritating while writing QB code
E131, # continuation line unaligned for hanging indent
E123, # closing bracket does not match indentation of opening bracket's line
E101, # ensured by use of black
max-line-length = 200
exclude=.github/helper/semgrep_rules

74
.github/helper/flake8.conf vendored Normal file
View File

@@ -0,0 +1,74 @@
[flake8]
ignore =
B001,
B007,
B009,
B010,
B950,
E101,
E111,
E114,
E116,
E117,
E121,
E122,
E123,
E124,
E125,
E126,
E127,
E128,
E131,
E201,
E202,
E203,
E211,
E221,
E222,
E223,
E224,
E225,
E226,
E228,
E231,
E241,
E242,
E251,
E261,
E262,
E265,
E266,
E271,
E272,
E273,
E274,
E301,
E302,
E303,
E305,
E306,
E402,
E501,
E502,
E701,
E702,
E703,
E741,
F401,
F403,
F405,
W191,
W291,
W292,
W293,
W391,
W503,
W504,
E711,
E129,
F841,
E713,
E712,
max-line-length = 200

33
.github/workflows/linters.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
name: Linters
on:
pull_request:
workflow_dispatch:
push:
branches: [ main ]
jobs:
linters:
name: Semantic Commits
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install and Run Pre-commit
uses: pre-commit/action@v2.0.3
- name: Download Semgrep rules
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules
- name: Download semgrep
run: pip install semgrep
- name: Run Semgrep rules
run: semgrep ci --config ./frappe-semgrep-rules/rules

59
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,59 @@
exclude: 'node_modules|.git'
default_stages: [commit]
fail_fast: false
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
hooks:
- id: trailing-whitespace
files: "frappe.*"
exclude: ".*json$|.*txt$|.*csv|.*md|.*svg"
- id: check-yaml
- id: no-commit-to-branch
args: ['--branch', 'main']
- id: check-merge-conflict
- id: check-ast
- id: check-json
- id: check-toml
- id: debug-statements
- repo: https://github.com/asottile/pyupgrade
rev: v2.34.0
hooks:
- id: pyupgrade
args: ['--py310-plus']
- repo: https://github.com/adityahase/black
rev: 9cb0a69f4d0030cdf687eddf314468b39ed54119
hooks:
- id: black
additional_dependencies: ['click==8.0.4']
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v2.7.1
hooks:
- id: prettier
types_or: [javascript]
# Ignore any files that might contain jinja / bundles
exclude: |
(?x)^(
lms/public/dist/.*|
.*node_modules.*|
.*boilerplate.*|
lms/www/website_script.js|
lms/templates/includes/.*|
lms/public/js/lib/.*
)$
- repo: https://gitlab.com/pycqa/flake8
rev: 3.9.2
hooks:
- id: flake8
additional_dependencies: ['flake8-bugbear',]
args: ['--config', '.github/helper/flake8.conf']
ci:
autoupdate_schedule: weekly
skip: []
submodules: false

View File

@@ -1,4 +1 @@
# -*- coding: utf-8 -*- __version__ = "0.0.1"
from __future__ import unicode_literals
__version__ = '0.0.1'

View File

@@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from frappe import _ from frappe import _
def get_data(): def get_data():
return [ return [
{ {
@@ -9,6 +8,6 @@ def get_data():
"color": "grey", "color": "grey",
"icon": "octicon octicon-file-directory", "icon": "octicon octicon-file-directory",
"type": "module", "type": "module",
"label": _("Community") "label": _("Community"),
} }
] ]

View File

@@ -7,5 +7,6 @@ Configuration for docs
# headline = "App that does everything" # headline = "App that does everything"
# sub_heading = "Yes, you got that right the first time, everything" # sub_heading = "Yes, you got that right the first time, everything"
def get_context(context): def get_context(context):
context.brand_html = "Community" context.brand_html = "Community"

View File

@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from . import __version__ as app_version from . import __version__ as app_version
app_name = "frappe_lms" app_name = "frappe_lms"
@@ -47,7 +45,7 @@ web_include_js = ["website.bundle.js"]
# website user home page (by Role) # website user home page (by Role)
# role_home_page = { # role_home_page = {
# "Role": "home_page" # "Role": "home_page"
# } # }
# Generators # Generators
@@ -87,8 +85,8 @@ after_uninstall = "lms.install.after_uninstall"
# Override standard doctype classes # Override standard doctype classes
override_doctype_class = { override_doctype_class = {
"User": "lms.overrides.user.CustomUser", "User": "lms.overrides.user.CustomUser",
"Web Template": "lms.overrides.web_template.CustomWebTemplate" "Web Template": "lms.overrides.web_template.CustomWebTemplate",
} }
# Document Events # Document Events
@@ -96,18 +94,16 @@ override_doctype_class = {
# Hook on document methods and events # Hook on document methods and events
doc_events = { doc_events = {
"Discussion Reply": { "Discussion Reply": {"after_insert": "lms.lms.utils.create_notification_log"}
"after_insert": "lms.lms.utils.create_notification_log"
}
} }
# Scheduled Tasks # Scheduled Tasks
# --------------- # ---------------
#scheduler_events = { # scheduler_events = {
# "daily": [ # "daily": [
# "erpnext.stock.reorder_item.reorder_item" # "erpnext.stock.reorder_item.reorder_item"
# ] # ]
#} # }
fixtures = ["Custom Field", "Function", "Industry"] fixtures = ["Custom Field", "Function", "Industry"]
@@ -136,79 +132,94 @@ fixtures = ["Custom Field", "Function", "Industry"]
# Add all simple route rules here # Add all simple route rules here
website_route_rules = [ website_route_rules = [
{"from_route": "/sketches/<sketch>", "to_route": "sketches/sketch"}, {"from_route": "/sketches/<sketch>", "to_route": "sketches/sketch"},
{"from_route": "/courses/<course>", "to_route": "courses/course"}, {"from_route": "/courses/<course>", "to_route": "courses/course"},
{"from_route": "/courses/<course>/<certificate>", "to_route": "courses/certificate"}, {"from_route": "/courses/<course>/<certificate>", "to_route": "courses/certificate"},
{"from_route": "/courses/<course>/learn", "to_route": "batch/learn"}, {"from_route": "/courses/<course>/learn", "to_route": "batch/learn"},
{"from_route": "/courses/<course>/learn/<int:chapter>.<int:lesson>", "to_route": "batch/learn"}, {
{"from_route": "/quizzes", "to_route": "batch/quiz_list"}, "from_route": "/courses/<course>/learn/<int:chapter>.<int:lesson>",
{"from_route": "/quizzes/<quizname>", "to_route": "batch/quiz"}, "to_route": "batch/learn",
{"from_route": "/courses/<course>/progress", "to_route": "batch/progress"}, },
{"from_route": "/courses/<course>/join", "to_route": "batch/join"}, {"from_route": "/quizzes", "to_route": "batch/quiz_list"},
{"from_route": "/courses/<course>/manage", "to_route": "cohorts"}, {"from_route": "/quizzes/<quizname>", "to_route": "batch/quiz"},
{"from_route": "/courses/<course>/cohorts/<cohort>", "to_route": "cohorts/cohort"}, {"from_route": "/courses/<course>/progress", "to_route": "batch/progress"},
{"from_route": "/courses/<course>/cohorts/<cohort>/<page>", "to_route": "cohorts/cohort"}, {"from_route": "/courses/<course>/join", "to_route": "batch/join"},
{"from_route": "/courses/<course>/subgroups/<cohort>/<subgroup>", "to_route": "cohorts/subgroup"}, {"from_route": "/courses/<course>/manage", "to_route": "cohorts"},
{"from_route": "/courses/<course>/subgroups/<cohort>/<subgroup>/<page>", "to_route": "cohorts/subgroup"}, {"from_route": "/courses/<course>/cohorts/<cohort>", "to_route": "cohorts/cohort"},
{"from_route": "/courses/<course>/join/<cohort>/<subgroup>/<invite_code>", "to_route": "cohorts/join"}, {
{"from_route": "/users", "to_route": "profiles/profile"}, "from_route": "/courses/<course>/cohorts/<cohort>/<page>",
{"from_route": "/jobs/<job>", "to_route": "jobs/job"} "to_route": "cohorts/cohort",
},
{
"from_route": "/courses/<course>/subgroups/<cohort>/<subgroup>",
"to_route": "cohorts/subgroup",
},
{
"from_route": "/courses/<course>/subgroups/<cohort>/<subgroup>/<page>",
"to_route": "cohorts/subgroup",
},
{
"from_route": "/courses/<course>/join/<cohort>/<subgroup>/<invite_code>",
"to_route": "cohorts/join",
},
{"from_route": "/users", "to_route": "profiles/profile"},
{"from_route": "/jobs/<job>", "to_route": "jobs/job"},
] ]
website_redirects = [ website_redirects = [
{"source": "/update-profile", "target": "/edit-profile"}, {"source": "/update-profile", "target": "/edit-profile"},
{"source": "/dashboard", "target": "/courses"}, {"source": "/dashboard", "target": "/courses"},
] ]
update_website_context = [ update_website_context = [
'lms.widgets.update_website_context', "lms.widgets.update_website_context",
] ]
jinja = { jinja = {
"methods": [ "methods": [
"lms.page_renderers.get_profile_url", "lms.page_renderers.get_profile_url",
"lms.overrides.user.get_enrolled_courses", "lms.overrides.user.get_enrolled_courses",
"lms.overrides.user.get_course_membership", "lms.overrides.user.get_course_membership",
"lms.overrides.user.get_authored_courses", "lms.overrides.user.get_authored_courses",
"lms.overrides.user.get_palette", "lms.overrides.user.get_palette",
"lms.lms.utils.get_membership", "lms.lms.utils.get_membership",
"lms.lms.utils.get_lessons", "lms.lms.utils.get_lessons",
"lms.lms.utils.get_tags", "lms.lms.utils.get_tags",
"lms.lms.utils.get_instructors", "lms.lms.utils.get_instructors",
"lms.lms.utils.get_students", "lms.lms.utils.get_students",
"lms.lms.utils.get_average_rating", "lms.lms.utils.get_average_rating",
"lms.lms.utils.is_certified", "lms.lms.utils.is_certified",
"lms.lms.utils.get_lesson_index", "lms.lms.utils.get_lesson_index",
"lms.lms.utils.get_lesson_url", "lms.lms.utils.get_lesson_url",
"lms.lms.utils.get_chapters", "lms.lms.utils.get_chapters",
"lms.lms.utils.get_slugified_chapter_title", "lms.lms.utils.get_slugified_chapter_title",
"lms.lms.utils.get_progress", "lms.lms.utils.get_progress",
"lms.lms.utils.render_html", "lms.lms.utils.render_html",
"lms.lms.utils.is_mentor", "lms.lms.utils.is_mentor",
"lms.lms.utils.is_cohort_staff", "lms.lms.utils.is_cohort_staff",
"lms.lms.utils.get_mentors", "lms.lms.utils.get_mentors",
"lms.lms.utils.get_reviews", "lms.lms.utils.get_reviews",
"lms.lms.utils.is_eligible_to_review", "lms.lms.utils.is_eligible_to_review",
"lms.lms.utils.get_initial_members", "lms.lms.utils.get_initial_members",
"lms.lms.utils.get_sorted_reviews", "lms.lms.utils.get_sorted_reviews",
"lms.lms.utils.is_instructor", "lms.lms.utils.is_instructor",
"lms.lms.utils.convert_number_to_character", "lms.lms.utils.convert_number_to_character",
"lms.lms.utils.get_signup_optin_checks", "lms.lms.utils.get_signup_optin_checks",
"lms.lms.utils.get_popular_courses", "lms.lms.utils.get_popular_courses",
"lms.lms.utils.format_amount", "lms.lms.utils.format_amount",
"lms.lms.utils.first_lesson_exists", "lms.lms.utils.first_lesson_exists",
"lms.lms.utils.get_courses_under_review", "lms.lms.utils.get_courses_under_review",
"lms.lms.utils.has_course_instructor_role", "lms.lms.utils.has_course_instructor_role",
"lms.lms.utils.has_course_moderator_role", "lms.lms.utils.has_course_moderator_role",
"lms.lms.utils.get_certificates", "lms.lms.utils.get_certificates",
"lms.lms.utils.format_number", "lms.lms.utils.format_number",
"lms.lms.utils.get_lesson_count", "lms.lms.utils.get_lesson_count",
"lms.lms.utils.get_all_memberships", "lms.lms.utils.get_all_memberships",
"lms.lms.utils.get_filtered_membership", "lms.lms.utils.get_filtered_membership",
"lms.lms.utils.show_start_learing_cta", "lms.lms.utils.show_start_learing_cta",
"lms.lms.utils.can_create_courses" "lms.lms.utils.can_create_courses",
], ],
"filters": [] "filters": [],
} }
## Specify the additional tabs to be included in the user profile page. ## Specify the additional tabs to be included in the user profile page.
## Each entry must be a subclass of lms.lms.plugins.ProfileTab ## Each entry must be a subclass of lms.lms.plugins.ProfileTab
@@ -219,42 +230,42 @@ jinja = {
## subclass of lms.plugins.PageExtension ## subclass of lms.plugins.PageExtension
# lms_lesson_page_extension = None # lms_lesson_page_extension = None
#lms_lesson_page_extensions = [ # lms_lesson_page_extensions = [
# "lms.plugins.LiveCodeExtension" # "lms.plugins.LiveCodeExtension"
#] # ]
profile_mandatory_fields = [ profile_mandatory_fields = [
"first_name", "first_name",
"last_name", "last_name",
"user_image", "user_image",
"bio", "bio",
"linkedin", "linkedin",
"education", "education",
"skill", "skill",
"preferred_functions", "preferred_functions",
"preferred_industries", "preferred_industries",
"dream_companies", "dream_companies",
"attire", "attire",
"collaboration", "collaboration",
"role", "role",
"location_preference", "location_preference",
"time", "time",
"company_type" "company_type",
] ]
## Markdown Macros for Lessons ## Markdown Macros for Lessons
lms_markdown_macro_renderers = { lms_markdown_macro_renderers = {
"Exercise": "lms.plugins.exercise_renderer", "Exercise": "lms.plugins.exercise_renderer",
"Quiz": "lms.plugins.quiz_renderer", "Quiz": "lms.plugins.quiz_renderer",
"YouTubeVideo": "lms.plugins.youtube_video_renderer", "YouTubeVideo": "lms.plugins.youtube_video_renderer",
"Video": "lms.plugins.video_renderer", "Video": "lms.plugins.video_renderer",
"Assignment": "lms.plugins.assignment_renderer" "Assignment": "lms.plugins.assignment_renderer",
} }
# page_renderer to manage profile pages # page_renderer to manage profile pages
page_renderer = [ page_renderer = [
"lms.page_renderers.ProfileRedirectPage", "lms.page_renderers.ProfileRedirectPage",
"lms.page_renderers.ProfilePage" "lms.page_renderers.ProfilePage",
] ]
# set this to "/" to have profiles on the top-level # set this to "/" to have profiles on the top-level

View File

@@ -3,58 +3,91 @@ from frappe.desk.page.setup_wizard.setup_wizard import add_all_roles_to
def after_sync(): def after_sync():
create_lms_roles() create_lms_roles()
set_default_home() set_default_home()
add_all_roles_to("Administrator") add_all_roles_to("Administrator")
def after_uninstall(): def after_uninstall():
delete_custom_fields() delete_custom_fields()
def create_lms_roles(): def create_lms_roles():
create_instructor_role() create_instructor_role()
create_moderator_role() create_moderator_role()
def set_default_home(): def set_default_home():
frappe.db.set_value("Portal Settings", None, "default_portal_home", "/courses") frappe.db.set_value("Portal Settings", None, "default_portal_home", "/courses")
def create_instructor_role(): def create_instructor_role():
if not frappe.db.exists("Role", "Course Instructor"): if not frappe.db.exists("Role", "Course Instructor"):
role = frappe.get_doc({ role = frappe.get_doc(
"doctype": "Role", {
"role_name": "Course Instructor", "doctype": "Role",
"home_page": "", "role_name": "Course Instructor",
"desk_access": 0 "home_page": "",
}) "desk_access": 0,
role.save(ignore_permissions=True) }
)
role.save(ignore_permissions=True)
def create_moderator_role(): def create_moderator_role():
if not frappe.db.exists("Role", "Course Moderator"): if not frappe.db.exists("Role", "Course Moderator"):
role = frappe.get_doc({ role = frappe.get_doc(
"doctype": "Role", {
"role_name": "Course Moderator", "doctype": "Role",
"home_page": "", "role_name": "Course Moderator",
"desk_access": 0 "home_page": "",
}) "desk_access": 0,
role.save(ignore_permissions=True) }
)
role.save(ignore_permissions=True)
def delete_custom_fields(): def delete_custom_fields():
fields = [ "user_category", "headline", "college", "city", "verify_terms", "country", fields = [
"preferred_location", "preferred_functions", "preferred_industries", "user_category",
"work_environment_column", "time", "role", "carrer_preference_details", "headline",
"skill", "certification_details", "internship", "branch", "github", "college",
"medium", "linkedin", "profession", "looking_for_job", "cover_image" "city",
"work_environment", "dream_companies", "career_preference_column", "verify_terms",
"attire", "collaboration", "location_preference", "company_type", "country",
"skill_details", "certification", "education", "work_experience", "preferred_location",
"education_details", "hide_private", "work_experience_details", "profile_complete" "preferred_functions",
] "preferred_industries",
"work_environment_column",
"time",
"role",
"carrer_preference_details",
"skill",
"certification_details",
"internship",
"branch",
"github",
"medium",
"linkedin",
"profession",
"looking_for_job",
"cover_image" "work_environment",
"dream_companies",
"career_preference_column",
"attire",
"collaboration",
"location_preference",
"company_type",
"skill_details",
"certification",
"education",
"work_experience",
"education_details",
"hide_private",
"work_experience_details",
"profile_complete",
]
for field in fields: for field in fields:
frappe.db.delete("Custom Field", {"fieldname": field}) frappe.db.delete("Custom Field", {"fieldname": field})
frappe.db.commit() frappe.db.commit()

View File

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

View File

@@ -2,40 +2,40 @@
# For license information, please see license.txt # For license information, please see license.txt
import frappe import frappe
from frappe.model.document import Document
from frappe.utils.user import get_system_managers
from frappe import _ from frappe import _
from frappe.model.document import Document
from frappe.utils import get_link_to_form from frappe.utils import get_link_to_form
from frappe.utils.user import get_system_managers
from lms.lms.utils import validate_image from lms.lms.utils import validate_image
class JobOpportunity(Document): class JobOpportunity(Document):
def validate(self):
self.validate_urls()
self.company_logo = validate_image(self.company_logo)
def validate_urls(self):
def validate(self): frappe.utils.validate_url(self.company_website, True)
self.validate_urls() frappe.utils.validate_url(self.application_link, True)
self.company_logo = validate_image(self.company_logo)
def validate_urls(self):
frappe.utils.validate_url(self.company_website, True)
frappe.utils.validate_url(self.application_link, True)
@frappe.whitelist() @frappe.whitelist()
def report(job, reason): def report(job, reason):
system_managers = get_system_managers(only_name=True) system_managers = get_system_managers(only_name=True)
user = frappe.db.get_value("User", frappe.session.user, "full_name") user = frappe.db.get_value("User", frappe.session.user, "full_name")
subject = _("User {0} has reported the job post {1}").format(user, job) subject = _("User {0} has reported the job post {1}").format(user, job)
args = { args = {
"job": job, "job": job,
"job_url": get_link_to_form("Job Opportunity", job), "job_url": get_link_to_form("Job Opportunity", job),
"user": user, "user": user,
"reason": reason "reason": reason,
} }
frappe.sendmail( frappe.sendmail(
recipients = system_managers, recipients=system_managers,
subject=subject, subject=subject,
header=[subject, "green"], header=[subject, "green"],
template = "job_report", template="job_report",
args=args, args=args,
now=True) now=True,
)

View File

@@ -4,5 +4,6 @@
# import frappe # import frappe
import unittest import unittest
class TestJobOpportunity(unittest.TestCase): class TestJobOpportunity(unittest.TestCase):
pass pass

View File

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

View File

@@ -4,5 +4,6 @@
# import frappe # import frappe
from frappe.model.document import Document from frappe.model.document import Document
class JobSettings(Document): class JobSettings(Document):
pass pass

View File

@@ -4,5 +4,6 @@
# import frappe # import frappe
import unittest import unittest
class TestJobSettings(unittest.TestCase): class TestJobSettings(unittest.TestCase):
pass pass

View File

@@ -1,5 +1,6 @@
import frappe import frappe
def get_context(context): def get_context(context):
# do your magic here # do your magic here
pass pass

View File

@@ -1,7 +1,7 @@
frappe.ready(function() { frappe.ready(function () {
frappe.web_form.after_save = () => { frappe.web_form.after_save = () => {
setTimeout(() => { setTimeout(() => {
window.location.href = `/jobs`; window.location.href = `/jobs`;
}) });
} };
}) });

View File

@@ -1,5 +1,6 @@
import frappe import frappe
def get_context(context): def get_context(context):
# do your magic here # do your magic here
pass pass

View File

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

View File

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

View File

@@ -4,5 +4,6 @@
# import frappe # import frappe
from frappe.model.document import Document from frappe.model.document import Document
class Certification(Document): class Certification(Document):
pass pass

View File

@@ -4,5 +4,6 @@
# import frappe # import frappe
import unittest import unittest
class TestCertification(unittest.TestCase): class TestCertification(unittest.TestCase):
pass pass

View File

@@ -4,5 +4,6 @@
# import frappe # import frappe
from frappe.model.document import Document from frappe.model.document import Document
class ChapterReference(Document): class ChapterReference(Document):
pass pass

View File

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

View File

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

View File

@@ -4,5 +4,6 @@
# import frappe # import frappe
import unittest import unittest
class TestCohort(unittest.TestCase): class TestCohort(unittest.TestCase):
pass pass

View File

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

View File

@@ -4,48 +4,49 @@
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
class CohortJoinRequest(Document): class CohortJoinRequest(Document):
def on_update(self): def on_update(self):
if self.status == "Accepted": if self.status == "Accepted":
self.ensure_student() self.ensure_student()
def ensure_student(self): def ensure_student(self):
# case 1 - user is already a member # case 1 - user is already a member
q = { q = {
"doctype": "LMS Batch Membership", "doctype": "LMS Batch Membership",
"cohort": self.cohort, "cohort": self.cohort,
"subgroup": self.subgroup, "subgroup": self.subgroup,
"member": self.email, "member": self.email,
"member_type": "Student" "member_type": "Student",
} }
if frappe.db.exists(q): if frappe.db.exists(q):
return return
# case 2 - user has signed up for this course, possibly not this cohort # case 2 - user has signed up for this course, possibly not this cohort
cohort = frappe.get_doc("Cohort", self.cohort) cohort = frappe.get_doc("Cohort", self.cohort)
q = { q = {
"doctype": "LMS Batch Membership", "doctype": "LMS Batch Membership",
"course": cohort.course, "course": cohort.course,
"member": self.email, "member": self.email,
"member_type": "Student" "member_type": "Student",
} }
name = frappe.db.exists(q) name = frappe.db.exists(q)
if name: if name:
doc = frappe.get_doc("LMS Batch Membership", name) doc = frappe.get_doc("LMS Batch Membership", name)
doc.cohort = self.cohort doc.cohort = self.cohort
doc.subgroup = self.subgroup doc.subgroup = self.subgroup
doc.save(ignore_permissions=True) doc.save(ignore_permissions=True)
else: else:
# case 3 - user has not signed up for this course yet # case 3 - user has not signed up for this course yet
data = { data = {
"doctype": "LMS Batch Membership", "doctype": "LMS Batch Membership",
"course": cohort.course, "course": cohort.course,
"cohort": self.cohort, "cohort": self.cohort,
"subgroup": self.subgroup, "subgroup": self.subgroup,
"member": self.email, "member": self.email,
"member_type": "Student", "member_type": "Student",
"role": "Member" "role": "Member",
} }
doc = frappe.get_doc(data) doc = frappe.get_doc(data)
doc.insert(ignore_permissions=True) doc.insert(ignore_permissions=True)

View File

@@ -4,5 +4,6 @@
# import frappe # import frappe
import unittest import unittest
class TestCohortJoinRequest(unittest.TestCase): class TestCohortJoinRequest(unittest.TestCase):
pass pass

View File

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

View File

@@ -4,9 +4,10 @@
import frappe import frappe
from frappe.model.document import Document 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): class CohortMentor(Document):
return frappe.get_doc("User", self.email) 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

@@ -4,5 +4,6 @@
# import frappe # import frappe
import unittest import unittest
class TestCohortMentor(unittest.TestCase): class TestCohortMentor(unittest.TestCase):
pass pass

View File

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

View File

@@ -4,5 +4,6 @@
# import frappe # import frappe
from frappe.model.document import Document from frappe.model.document import Document
class CohortStaff(Document): class CohortStaff(Document):
pass pass

View File

@@ -4,5 +4,6 @@
# import frappe # import frappe
import unittest import unittest
class TestCohortStaff(unittest.TestCase): class TestCohortStaff(unittest.TestCase):
pass pass

View File

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

View File

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

View File

@@ -4,5 +4,6 @@
# import frappe # import frappe
import unittest import unittest
class TestCohortSubgroup(unittest.TestCase): class TestCohortSubgroup(unittest.TestCase):
pass pass

View File

@@ -4,6 +4,7 @@
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
class CohortWebPage(Document): class CohortWebPage(Document):
def get_template_html(self): def get_template_html(self):
return frappe.get_doc("Web Template", self.template).template return frappe.get_doc("Web Template", self.template).template

View File

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

View File

@@ -4,5 +4,6 @@
# import frappe # import frappe
from frappe.model.document import Document from frappe.model.document import Document
class CourseChapter(Document): class CourseChapter(Document):
pass pass

View File

@@ -4,5 +4,6 @@
# import frappe # import frappe
import unittest import unittest
class TestCourseChapter(unittest.TestCase): class TestCourseChapter(unittest.TestCase):
pass pass

View File

@@ -1,14 +1,14 @@
// Copyright (c) 2022, Frappe and contributors // Copyright (c) 2022, Frappe and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Course Evaluator', { frappe.ui.form.on("Course Evaluator", {
onload: (frm) => { onload: (frm) => {
frm.set_query('evaluator', function(doc) { frm.set_query("evaluator", function (doc) {
return { return {
filters: { filters: {
"ignore_user_type": 1, ignore_user_type: 1,
} },
}; };
}); });
} },
}); });

View File

@@ -5,46 +5,53 @@ import frappe
from frappe import _ from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
class CourseEvaluator(Document): class CourseEvaluator(Document):
def validate(self):
self.validate_time_slots()
def validate(self): def validate_time_slots(self):
self.validate_time_slots() for schedule in self.schedule:
if schedule.start_time >= schedule.end_time:
frappe.throw(_("Start Time cannot be greater than End Time"))
def validate_time_slots(self): self.validate_overlaps(schedule)
for schedule in self.schedule:
if schedule.start_time >= schedule.end_time:
frappe.throw(_("Start Time cannot be greater than End Time"))
self.validate_overlaps(schedule) def validate_overlaps(self, schedule):
same_day_slots = list(
filter(lambda x: x.day == schedule.day and x.name != schedule.name, self.schedule)
)
overlap = False
def validate_overlaps(self, schedule): for slot in same_day_slots:
same_day_slots = list(filter(lambda x: x.day == schedule.day and x.name != schedule.name , self.schedule)) if schedule.start_time <= slot.start_time < schedule.end_time:
overlap = False overlap = True
if schedule.start_time < slot.end_time <= schedule.end_time:
overlap = True
if slot.start_time < schedule.start_time and schedule.end_time < slot.end_time:
overlap = True
for slot in same_day_slots: if overlap:
if schedule.start_time <= slot.start_time < schedule.end_time: frappe.throw(_("Slot Times are overlapping for some schedules."))
overlap = True
if schedule.start_time < slot.end_time <= schedule.end_time:
overlap = True
if slot.start_time < schedule.start_time and schedule.end_time < slot.end_time:
overlap = True
if overlap:
frappe.throw(_("Slot Times are overlapping for some schedules."))
@frappe.whitelist() @frappe.whitelist()
def get_schedule(course, date): def get_schedule(course, date):
evaluator = frappe.db.get_value("LMS Course", course, "evaluator") evaluator = frappe.db.get_value("LMS Course", course, "evaluator")
all_slots = frappe.get_all("Evaluator Schedule", all_slots = frappe.get_all(
filters = { "parent": evaluator }, "Evaluator Schedule",
fields = ["day", "start_time", "end_time"]) filters={"parent": evaluator},
booked_slots = frappe.get_all("LMS Certificate Request", fields=["day", "start_time", "end_time"],
filters = {"evaluator": evaluator, "date": date}, )
fields = ["start_time"]) booked_slots = frappe.get_all(
"LMS Certificate Request",
filters={"evaluator": evaluator, "date": date},
fields=["start_time"],
)
for slot in booked_slots: for slot in booked_slots:
same_slot = list(filter(lambda x: x.start_time == slot.start_time, all_slots)) same_slot = list(filter(lambda x: x.start_time == slot.start_time, all_slots))
if len(same_slot): if len(same_slot):
all_slots.remove(same_slot[0]) all_slots.remove(same_slot[0])
return all_slots return all_slots

View File

@@ -4,5 +4,6 @@
# import frappe # import frappe
from frappe.model.document import Document from frappe.model.document import Document
class CourseInstructor(Document): class CourseInstructor(Document):
pass pass

View File

@@ -1,17 +1,21 @@
// Copyright (c) 2021, FOSS United and contributors // Copyright (c) 2021, FOSS United and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Course Lesson', { frappe.ui.form.on("Course Lesson", {
setup: function (frm) { setup: function (frm) {
frm.trigger('setup_help'); frm.trigger("setup_help");
}, },
setup_help(frm) { setup_help(frm) {
let quiz_link = `<a href="/app/lms-quiz"> ${__("Quiz List")} </a>`; let quiz_link = `<a href="/app/lms-quiz"> ${__("Quiz List")} </a>`;
let exercise_link = `<a href="/app/exercise"> ${__("Exercise List")} </a>`; let exercise_link = `<a href="/app/exercise"> ${__(
let file_link = `<a href="/app/file"> ${__("File DocType")} </a>`; "Exercise List"
)} </a>`;
let file_link = `<a href="/app/file"> ${__("File DocType")} </a>`;
frm.get_field('help').html(` 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> <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>
<table class="table"> <table class="table">
<tr style="background-color: var(--fg-hover-color); font-weight: bold"> <tr style="background-color: var(--fg-hover-color); font-weight: bold">
<th style="width: 20%;"> <th style="width: 20%;">
@@ -33,17 +37,23 @@ frappe.ui.form.on('Course Lesson', {
</td> </td>
<td> <td>
<span> <span>
${ __("Copy and paste the syntax in the editor. Replace 'embed_src' with the embed source that YouTube provides. To get the source, follow the steps mentioned below.") } ${__(
"Copy and paste the syntax in the editor. Replace 'embed_src' with the embed source that YouTube provides. To get the source, follow the steps mentioned below."
)}
</span> </span>
<ul class="p-4"> <ul class="p-4">
<li> <li>
${ __("Upload the video on youtube.") } ${__("Upload the video on youtube.")}
</li> </li>
<li> <li>
${ __("When you share a youtube video, it shows an option called Embed.") } ${__(
"When you share a youtube video, it shows an option called Embed."
)}
</li> </li>
<li> <li>
${ __("On clicking it, it provides an iframe. Copy the source (src) of the iframe and paste it here.") } ${__(
"On clicking it, it provides an iframe. Copy the source (src) of the iframe and paste it here."
)}
</li> </li>
</ul> </ul>
</td> </td>
@@ -56,7 +66,10 @@ frappe.ui.form.on('Course Lesson', {
{{ Quiz("lms_quiz_id") }} {{ Quiz("lms_quiz_id") }}
</td> </td>
<td> <td>
${ __("Copy and paste the syntax in the editor. Replace 'lms_quiz_id' with the ID of the Quiz you want to add. You can get the ID of the quiz from the {0}.", [quiz_link]) } ${__(
"Copy and paste the syntax in the editor. Replace 'lms_quiz_id' with the ID of the Quiz you want to add. You can get the ID of the quiz from the {0}.",
[quiz_link]
)}
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -67,7 +80,10 @@ frappe.ui.form.on('Course Lesson', {
{{ Video("url_of_source") }} {{ Video("url_of_source") }}
</td> </td>
<td> <td>
${ __("Upload a video from your local machine to the {0}. Copy and paste this syntax in the editor. Replace 'url_of_source' with the File URL field of the document you created in the File DocType.", [file_link]) } ${__(
"Upload a video from your local machine to the {0}. Copy and paste this syntax in the editor. Replace 'url_of_source' with the File URL field of the document you created in the File DocType.",
[file_link]
)}
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -78,7 +94,10 @@ frappe.ui.form.on('Course Lesson', {
{{ Exercise("exercise_id") }} {{ Exercise("exercise_id") }}
</td> </td>
<td> <td>
${ __("Copy and paste the syntax in the editor. Replace 'exercise_id' with the ID of the Exercise you want to add. You can get the ID of the exercise from the {0}.", [exercise_link]) } ${__(
"Copy and paste the syntax in the editor. Replace 'exercise_id' with the ID of the Exercise you want to add. You can get the ID of the exercise from the {0}.",
[exercise_link]
)}
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -125,5 +144,5 @@ frappe.ui.form.on('Course Lesson', {
</tr> </tr>
</table> </table>
`); `);
} },
}); });

View File

@@ -1,125 +1,119 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021, FOSS United and contributors # Copyright (c) 2021, FOSS United and contributors
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
from ...md import find_macros
from lms.lms.utils import get_course_progress, get_lesson_url from lms.lms.utils import get_course_progress, get_lesson_url
from ...md import find_macros
class CourseLesson(Document): class CourseLesson(Document):
def validate(self): def validate(self):
#self.check_and_create_folder() # self.check_and_create_folder()
self.validate_quiz_id() self.validate_quiz_id()
def validate_quiz_id(self):
if self.quiz_id and not frappe.db.exists("LMS Quiz", self.quiz_id):
frappe.throw(_("Invalid Quiz ID"))
def validate_quiz_id(self): def on_update(self):
if self.quiz_id and not frappe.db.exists("LMS Quiz", self.quiz_id): dynamic_documents = ["Exercise", "Quiz"]
frappe.throw(_("Invalid Quiz ID")) 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(ignore_permissions=True)
index += 1
self.update_orphan_documents(doctype_map[section], documents)
def on_update(self): def update_orphan_documents(self, doctype, documents):
dynamic_documents = ["Exercise", "Quiz"] """Updates the documents that were previously part of this lesson,
for section in dynamic_documents: but not any more.
self.update_lesson_name_in_document(section) """
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):
args = {
"doctype": "File",
"is_folder": True,
"file_name": f"{self.name} {self.course}",
}
if not frappe.db.exists(args):
folder = frappe.get_doc(args)
folder.save(ignore_permissions=True)
def update_lesson_name_in_document(self, section): def get_exercises(self):
doctype_map= { if not self.body:
"Exercise": "Exercise", return []
"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(ignore_permissions=True)
index += 1
self.update_orphan_documents(doctype_map[section], documents)
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 update_orphan_documents(self, doctype, documents): def get_progress(self):
"""Updates the documents that were previously part of this lesson, return frappe.db.get_value(
but not any more. "LMS Course Progress", {"lesson": self.name, "owner": frappe.session.user}, "status"
""" )
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 get_slugified_class(self):
def check_and_create_folder(self): if self.get_progress():
args = { return ("").join([s for s in self.get_progress().lower().split()])
"doctype": "File", return
"is_folder": True,
"file_name": f"{self.name} {self.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() @frappe.whitelist()
def save_progress(lesson, course, status): def save_progress(lesson, course, status):
membership = frappe.db.exists("LMS Batch Membership", { membership = frappe.db.exists(
"member": frappe.session.user, "LMS Batch Membership", {"member": frappe.session.user, "course": course}
"course": course )
}) if not membership:
if not membership: return
return
if frappe.db.exists("LMS Course Progress", { if frappe.db.exists(
"lesson": lesson, "LMS Course Progress",
"owner": frappe.session.user, {"lesson": lesson, "owner": frappe.session.user, "course": course},
"course": course ):
}): doc = frappe.get_doc(
doc = frappe.get_doc("LMS Course Progress", { "LMS Course Progress",
"lesson": lesson, {"lesson": lesson, "owner": frappe.session.user, "course": course},
"owner": frappe.session.user, )
"course": course doc.status = status
}) doc.save(ignore_permissions=True)
doc.status = status else:
doc.save(ignore_permissions=True) frappe.get_doc(
else: {
frappe.get_doc({ "doctype": "LMS Course Progress",
"doctype": "LMS Course Progress", "lesson": lesson,
"lesson": lesson, "status": status,
"status": status, }
}).save(ignore_permissions=True) ).save(ignore_permissions=True)
progress = get_course_progress(course) progress = get_course_progress(course)
frappe.db.set_value("LMS Batch Membership", membership, "progress", progress) frappe.db.set_value("LMS Batch Membership", membership, "progress", progress)
return progress return progress
@frappe.whitelist() @frappe.whitelist()
def get_lesson_info(chapter): def get_lesson_info(chapter):
return frappe.db.get_value("Course Chapter", chapter, "course") return frappe.db.get_value("Course Chapter", chapter, "course")

View File

@@ -4,5 +4,6 @@
# import frappe # import frappe
import unittest import unittest
class TestCourseLesson(unittest.TestCase): class TestCourseLesson(unittest.TestCase):
pass pass

View File

@@ -4,5 +4,6 @@
# import frappe # import frappe
from frappe.model.document import Document from frappe.model.document import Document
class EducationDetail(Document): class EducationDetail(Document):
pass pass

View File

@@ -4,5 +4,6 @@
# import frappe # import frappe
from frappe.model.document import Document from frappe.model.document import Document
class EvaluatorSchedule(Document): class EvaluatorSchedule(Document):
pass pass

View File

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

View File

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

View File

@@ -1,50 +1,54 @@
# Copyright (c) 2021, FOSS United and Contributors # Copyright (c) 2021, FOSS United and Contributors
# See license.txt # See license.txt
import frappe
import unittest import unittest
import frappe
from lms.lms.doctype.lms_course.test_lms_course import new_course from lms.lms.doctype.lms_course.test_lms_course import new_course
class TestExercise(unittest.TestCase): class TestExercise(unittest.TestCase):
def new_exercise(self):
course = new_course("Test Course")
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 new_exercise(self): def test_exercise(self):
course = new_course("Test Course") e = self.new_exercise()
member = frappe.get_doc({ assert e.get_user_submission() is None
"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): def test_exercise_submission(self):
e = self.new_exercise() e = self.new_exercise()
assert e.get_user_submission() is None submission = e.submit("circle(100, 100, 50)")
assert submission is not None
assert submission.exercise == e.name
assert submission.course == e.course
def test_exercise_submission(self): user_submission = e.get_user_submission()
e = self.new_exercise() assert user_submission is not None
submission = e.submit("circle(100, 100, 50)") assert user_submission.name == submission.name
assert submission is not None
assert submission.exercise == e.name
assert submission.course == e.course
user_submission = e.get_user_submission() def tearDown(self):
assert user_submission is not None frappe.db.sql("delete from `tabLMS Batch Membership`")
assert user_submission.name == submission.name frappe.db.sql("delete from `tabExercise Submission`")
frappe.db.sql("delete from `tabExercise`")
def tearDown(self):
frappe.db.sql('delete from `tabLMS Batch Membership`')
frappe.db.sql('delete from `tabExercise Submission`')
frappe.db.sql('delete from `tabExercise`')

View File

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

View File

@@ -4,5 +4,6 @@
# import frappe # import frappe
from frappe.model.document import Document from frappe.model.document import Document
class ExerciseLatestSubmission(Document): class ExerciseLatestSubmission(Document):
pass pass

View File

@@ -4,5 +4,6 @@
# import frappe # import frappe
import unittest import unittest
class TestExerciseLatestSubmission(unittest.TestCase): class TestExerciseLatestSubmission(unittest.TestCase):
pass pass

View File

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

View File

@@ -4,21 +4,26 @@
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
class ExerciseSubmission(Document):
def on_update(self):
self.update_latest_submission()
def update_latest_submission(self): class ExerciseSubmission(Document):
names = frappe.get_all("Exercise Latest Submission", {"exercise": self.exercise, "member": self.member}) def on_update(self):
if names: self.update_latest_submission()
doc = frappe.get_doc("Exercise Latest Submission", names[0])
doc.latest_submission = self.name def update_latest_submission(self):
doc.save(ignore_permissions=True) names = frappe.get_all(
else: "Exercise Latest Submission", {"exercise": self.exercise, "member": self.member}
doc = frappe.get_doc({ )
"doctype": "Exercise Latest Submission", if names:
"exercise": self.exercise, doc = frappe.get_doc("Exercise Latest Submission", names[0])
"member": self.member, doc.latest_submission = self.name
"latest_submission": self.name doc.save(ignore_permissions=True)
}) else:
doc.insert(ignore_permissions=True) 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

@@ -4,5 +4,6 @@
# import frappe # import frappe
import unittest import unittest
class TestExerciseSubmission(unittest.TestCase): class TestExerciseSubmission(unittest.TestCase):
pass pass

View File

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

View File

@@ -4,5 +4,6 @@
# import frappe # import frappe
from frappe.model.document import Document from frappe.model.document import Document
class Function(Document): class Function(Document):
pass pass

View File

@@ -4,5 +4,6 @@
# import frappe # import frappe
import unittest import unittest
class TestFunction(unittest.TestCase): class TestFunction(unittest.TestCase):
pass pass

View File

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

View File

@@ -4,5 +4,6 @@
# import frappe # import frappe
from frappe.model.document import Document from frappe.model.document import Document
class Industry(Document): class Industry(Document):
pass pass

View File

@@ -4,5 +4,6 @@
# import frappe # import frappe
import unittest import unittest
class TestIndustry(unittest.TestCase): class TestIndustry(unittest.TestCase):
pass pass

View File

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

View File

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

View File

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

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

View File

@@ -2,48 +2,51 @@
# For license information, please see license.txt # For license information, please see license.txt
import frappe import frappe
from frappe.model.document import Document
from frappe import _ from frappe import _
from frappe.model.document import Document
class LessonAssignment(Document): class LessonAssignment(Document):
def validate(self): def validate(self):
self.validate_duplicates() self.validate_duplicates()
def validate_duplicates(self):
def validate_duplicates(self): if frappe.db.exists(
if frappe.db.exists("Lesson Assignment", {"lesson": self.lesson, "member": self.member}): "Lesson Assignment", {"lesson": self.lesson, "member": self.member}
lesson_title = frappe.db.get_value("Course Lesson", self.lesson, "title") ):
frappe.throw(_("Assignment for Lesson {0} by {1} already exists.").format(lesson_title, self.member_name)) lesson_title = frappe.db.get_value("Course Lesson", self.lesson, "title")
frappe.throw(
_("Assignment for Lesson {0} by {1} already exists.").format(
lesson_title, self.member_name
)
)
@frappe.whitelist() @frappe.whitelist()
def upload_assignment(assignment, lesson): def upload_assignment(assignment, lesson):
args = { args = {
"doctype": "Lesson Assignment", "doctype": "Lesson Assignment",
"lesson": lesson, "lesson": lesson,
"member": frappe.session.user "member": frappe.session.user,
} }
if frappe.db.exists(args): if frappe.db.exists(args):
del args["doctype"] del args["doctype"]
frappe.db.set_value("Lesson Assignment", args, "assignment", assignment) frappe.db.set_value("Lesson Assignment", args, "assignment", assignment)
else: else:
args.update({"assignment": assignment}) args.update({"assignment": assignment})
lesson_work = frappe.get_doc(args) lesson_work = frappe.get_doc(args)
lesson_work.save(ignore_permissions=True) lesson_work.save(ignore_permissions=True)
@frappe.whitelist() @frappe.whitelist()
def get_assignment(lesson): def get_assignment(lesson):
assignment = frappe.db.get_value("Lesson Assignment", { assignment = frappe.db.get_value(
"lesson": lesson, "Lesson Assignment",
"member": frappe.session.user {"lesson": lesson, "member": frappe.session.user},
}, ["lesson", "member", "assignment"], ["lesson", "member", "assignment"],
as_dict=True) as_dict=True,
assignment.file_name = frappe.db.get_value("File", {"file_url": assignment.assignment}, "file_name") )
return assignment assignment.file_name = frappe.db.get_value(
"File", {"file_url": assignment.assignment}, "file_name"
)
return assignment

View File

@@ -4,5 +4,6 @@
# import frappe # import frappe
import unittest import unittest
class TestLessonAssignment(unittest.TestCase): class TestLessonAssignment(unittest.TestCase):
pass pass

View File

@@ -4,5 +4,6 @@
# import frappe # import frappe
from frappe.model.document import Document from frappe.model.document import Document
class LessonReference(Document): class LessonReference(Document):
pass pass

View File

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

View File

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

View File

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

View File

@@ -1,14 +1,14 @@
// Copyright (c) 2021, FOSS United and contributors // Copyright (c) 2021, FOSS United and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('LMS Batch Membership', { frappe.ui.form.on("LMS Batch Membership", {
onload: function(frm) { onload: function (frm) {
frm.set_query('member', function(doc) { frm.set_query("member", function (doc) {
return { return {
filters: { filters: {
"ignore_user_type": 1, ignore_user_type: 1,
} },
}; };
}); });
} },
}); });

View File

@@ -1,78 +1,90 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021, FOSS United and contributors # Copyright (c) 2021, FOSS United and contributors
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals
import frappe import frappe
from frappe.model.document import Document
from frappe import _ from frappe import _
from frappe.model.document import Document
class LMSBatchMembership(Document): class LMSBatchMembership(Document):
def validate(self):
self.validate_membership_in_same_batch()
self.validate_membership_in_different_batch_same_course()
def validate(self): def validate_membership_in_same_batch(self):
self.validate_membership_in_same_batch() filters = {"member": self.member, "course": self.course, "name": ["!=", self.name]}
self.validate_membership_in_different_batch_same_course() if self.batch:
filters["batch"] = self.batch
previous_membership = frappe.db.get_value(
"LMS Batch Membership", filters, fieldname=["member_type", "member"], as_dict=1
)
def validate_membership_in_same_batch(self): if previous_membership:
filters={ member_name = frappe.db.get_value("User", self.member, "full_name")
"member": self.member, course_title = frappe.db.get_value("LMS Course", self.course, "title")
"course": self.course, frappe.throw(
"name": ["!=", self.name] _("{0} is already a {1} of the course {2}").format(
} member_name, previous_membership.member_type, course_title
if self.batch: )
filters["batch"] = self.batch )
previous_membership = frappe.db.get_value("LMS Batch Membership",
filters,
fieldname=["member_type","member"],
as_dict=1)
if previous_membership: def validate_membership_in_different_batch_same_course(self):
member_name = frappe.db.get_value("User", self.member, "full_name") """Ensures that a studnet is only part of one batch."""
course_title = frappe.db.get_value("LMS Course", self.course, "title") # nothing to worry if the member is not a student
frappe.throw(_("{0} is already a {1} of the course {2}").format(member_name, previous_membership.member_type, course_title)) if self.member_type != "Student":
return
def validate_membership_in_different_batch_same_course(self): course = frappe.db.get_value("LMS Batch", self.batch, "course")
"""Ensures that a studnet is only part of one batch. memberships = frappe.get_all(
""" "LMS Batch Membership",
# nothing to worry if the member is not a student filters={
if self.member_type != "Student": "member": self.member,
return "name": ["!=", self.name],
"member_type": "Student",
"course": self.course,
},
fields=["batch", "member_type", "name"],
)
course = frappe.db.get_value("LMS Batch", self.batch, "course") if memberships:
memberships = frappe.get_all( membership = memberships[0]
"LMS Batch Membership", member_name = frappe.db.get_value("User", self.member, "full_name")
filters={ frappe.throw(
"member": self.member, _("{0} is already a Student of {1} course through {2} batch").format(
"name": ["!=", self.name], member_name, course, membership.batch
"member_type": "Student", )
"course": self.course )
},
fields=["batch", "member_type", "name"]
)
if memberships:
membership = memberships[0]
member_name = frappe.db.get_value("User", self.member, "full_name")
frappe.throw(_("{0} is already a Student of {1} course through {2} batch").format(member_name, course, membership.batch))
@frappe.whitelist() @frappe.whitelist()
def create_membership(course, batch=None, member=None, member_type="Student", role="Member"): def create_membership(
frappe.get_doc({ course, batch=None, member=None, member_type="Student", role="Member"
"doctype": "LMS Batch Membership", ):
"batch": batch, frappe.get_doc(
"course": course, {
"role": role, "doctype": "LMS Batch Membership",
"member_type": member_type, "batch": batch,
"member": member or frappe.session.user "course": course,
}).save(ignore_permissions=True) "role": role,
return "OK" "member_type": member_type,
"member": member or frappe.session.user,
}
).save(ignore_permissions=True)
return "OK"
@frappe.whitelist() @frappe.whitelist()
def update_current_membership(batch, course, member): def update_current_membership(batch, course, member):
all_memberships = frappe.get_all("LMS Batch Membership", {"member": member, "course": course}) all_memberships = frappe.get_all(
for membership in all_memberships: "LMS Batch Membership", {"member": member, "course": course}
frappe.db.set_value("LMS Batch Membership", membership.name, "is_current", 0) )
for membership in all_memberships:
frappe.db.set_value("LMS Batch Membership", membership.name, "is_current", 0)
current_membership = frappe.get_all("LMS Batch Membership", {"batch": batch, "member": member}) current_membership = frappe.get_all(
if len(current_membership): "LMS Batch Membership", {"batch": batch, "member": member}
frappe.db.set_value("LMS Batch Membership", current_membership[0].name, "is_current", 1) )
if len(current_membership):
frappe.db.set_value(
"LMS Batch Membership", current_membership[0].name, "is_current", 1
)

View File

@@ -1,62 +1,67 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021, FOSS United and Contributors # Copyright (c) 2021, FOSS United and Contributors
# See license.txt # See license.txt
from __future__ import unicode_literals
import unittest
import frappe import frappe
import unittest
from lms.lms.doctype.lms_course.test_lms_course import new_user, new_course from lms.lms.doctype.lms_course.test_lms_course import new_course, new_user
class TestLMSBatchMembership(unittest.TestCase): class TestLMSBatchMembership(unittest.TestCase):
def setUp(self): def setUp(self):
frappe.db.sql("DELETE FROM `tabLMS Batch Membership`") frappe.db.sql("DELETE FROM `tabLMS Batch Membership`")
frappe.db.sql("DELETE FROM `tabLMS Batch`") frappe.db.sql("DELETE FROM `tabLMS Batch`")
frappe.db.sql('delete from `tabLMS Course Mentor Mapping`') frappe.db.sql("delete from `tabLMS Course Mentor Mapping`")
frappe.db.sql("DELETE FROM `tabUser` where email like '%@test.com'") frappe.db.sql("DELETE FROM `tabUser` where email like '%@test.com'")
def new_course_batch(self): def new_course_batch(self):
course = new_course("Test Course") course = new_course("Test Course")
new_user("Test Mentor", "mentor@test.com") new_user("Test Mentor", "mentor@test.com")
# without this, the creating batch will fail # without this, the creating batch will fail
course.add_mentor("mentor@test.com") course.add_mentor("mentor@test.com")
frappe.session.user = "mentor@test.com" frappe.session.user = "mentor@test.com"
batch = frappe.get_doc({ batch = frappe.get_doc(
"doctype": "LMS Batch", {
"name": "test-batch", "doctype": "LMS Batch",
"title": "Test Batch", "name": "test-batch",
"course": course.name "title": "Test Batch",
}) "course": course.name,
batch.insert(ignore_permissions=True) }
)
batch.insert(ignore_permissions=True)
frappe.session.user = "Administrator" frappe.session.user = "Administrator"
return course, batch return course, batch
def add_membership(self, batch_name, member_name, member_type="Student"): def add_membership(self, batch_name, member_name, member_type="Student"):
doc = frappe.get_doc({ doc = frappe.get_doc(
"doctype": "LMS Batch Membership", {
"batch": batch_name, "doctype": "LMS Batch Membership",
"member": member_name, "batch": batch_name,
"member_type": member_type "member": member_name,
}) "member_type": member_type,
doc.insert() }
return doc )
doc.insert()
return doc
def test_membership(self): def test_membership(self):
course, batch = self.new_course_batch() course, batch = self.new_course_batch()
member = new_user("Test", "test01@test.com") member = new_user("Test", "test01@test.com")
membership = self.add_membership(batch.name, member.name) membership = self.add_membership(batch.name, member.name)
assert membership.course == course.name assert membership.course == course.name
assert membership.member_name == member.full_name assert membership.member_name == member.full_name
def test_membership_change_role(self): def test_membership_change_role(self):
course, batch = self.new_course_batch() course, batch = self.new_course_batch()
member = new_user("Test", "test01@test.com") member = new_user("Test", "test01@test.com")
membership = self.add_membership(batch.name, member.name) membership = self.add_membership(batch.name, member.name)
# it should be possible to change role # it should be possible to change role
membership.role = "Admin" membership.role = "Admin"
membership.save() membership.save()

View File

@@ -1,17 +1,21 @@
// Copyright (c) 2021, FOSS United and contributors // Copyright (c) 2021, FOSS United and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('LMS Certificate', { frappe.ui.form.on("LMS Certificate", {
onload: (frm) => { onload: (frm) => {
frm.set_query("member", function (doc) { frm.set_query("member", function (doc) {
return { return {
filters: { filters: {
"ignore_user_type": 1, ignore_user_type: 1,
} },
}; };
}); });
}, },
refresh: (frm) => { refresh: (frm) => {
if (frm.doc.name) frm.add_web_link(`/courses/${frm.doc.course}/${frm.doc.name}`, 'See on Website') if (frm.doc.name)
} frm.add_web_link(
`/courses/${frm.doc.course}/${frm.doc.name}`,
"See on Website"
);
},
}); });

View File

@@ -2,49 +2,55 @@
# For license information, please see license.txt # For license information, please see license.txt
import frappe import frappe
from frappe.model.document import Document
from frappe.utils import nowdate, add_years
from frappe import _ from frappe import _
from frappe.model.document import Document
from frappe.utils import add_years, nowdate
from frappe.utils.pdf import get_pdf from frappe.utils.pdf import get_pdf
from lms.lms.utils import is_certified from lms.lms.utils import is_certified
class LMSCertificate(Document):
def before_insert(self): class LMSCertificate(Document):
certificates = frappe.get_all("LMS Certificate", { def before_insert(self):
"member": self.member, certificates = frappe.get_all(
"course": self.course "LMS Certificate", {"member": self.member, "course": self.course}
}) )
if len(certificates): if len(certificates):
full_name = frappe.db.get_value("User", self.member, "full_name") full_name = frappe.db.get_value("User", self.member, "full_name")
course_name = frappe.db.get_value("LMS Course", self.course, "title") course_name = frappe.db.get_value("LMS Course", self.course, "title")
frappe.throw(_("{0} is already certified for the course {1}").format(full_name, course_name)) frappe.throw(
_("{0} is already certified for the course {1}").format(full_name, course_name)
)
@frappe.whitelist() @frappe.whitelist()
def create_certificate(course): def create_certificate(course):
certificate = is_certified(course) certificate = is_certified(course)
if certificate: if certificate:
return certificate return certificate
else: else:
expires_after_yrs = int(frappe.db.get_value("LMS Course", course, "expiry")) expires_after_yrs = int(frappe.db.get_value("LMS Course", course, "expiry"))
expiry_date = None expiry_date = None
if expires_after_yrs: if expires_after_yrs:
expiry_date = add_years(nowdate(), expires_after_yrs) expiry_date = add_years(nowdate(), expires_after_yrs)
certificate = frappe.get_doc(
{
"doctype": "LMS Certificate",
"member": frappe.session.user,
"course": course,
"issue_date": nowdate(),
"expiry_date": expiry_date,
}
)
certificate.save(ignore_permissions=True)
return certificate
certificate = frappe.get_doc({
"doctype": "LMS Certificate",
"member": frappe.session.user,
"course": course,
"issue_date": nowdate(),
"expiry_date": expiry_date
})
certificate.save(ignore_permissions=True)
return certificate
@frappe.whitelist() @frappe.whitelist()
def get_certificate_pdf(html): def get_certificate_pdf(html):
frappe.local.response.filename = "certificate.pdf" frappe.local.response.filename = "certificate.pdf"
frappe.local.response.filecontent = get_pdf(html, {"orientation": "LandScape"}) frappe.local.response.filecontent = get_pdf(html, {"orientation": "LandScape"})
frappe.local.response.type = "pdf" frappe.local.response.type = "pdf"

View File

@@ -1,25 +1,24 @@
# Copyright (c) 2021, FOSS United and Contributors # Copyright (c) 2021, FOSS United and Contributors
# See license.txt # See license.txt
import frappe
import unittest import unittest
from lms.lms.doctype.lms_course.test_lms_course import new_course
import frappe
from frappe.utils import add_years, cint, nowdate
from lms.lms.doctype.lms_certificate.lms_certificate import create_certificate from lms.lms.doctype.lms_certificate.lms_certificate import create_certificate
from frappe.utils import nowdate, add_years, cint from lms.lms.doctype.lms_course.test_lms_course import new_course
class TestLMSCertificate(unittest.TestCase): class TestLMSCertificate(unittest.TestCase):
def test_certificate_creation(self):
course = new_course("Test Certificate", {"enable_certification": 1, "expiry": 2})
certificate = create_certificate(course.name)
def test_certificate_creation(self): self.assertEqual(certificate.member, "Administrator")
course = new_course("Test Certificate", { self.assertEqual(certificate.course, course.name)
"enable_certification": 1, self.assertEqual(certificate.issue_date, nowdate())
"expiry": 2 self.assertEqual(certificate.expiry_date, add_years(nowdate(), cint(course.expiry)))
})
certificate = create_certificate(course.name)
self.assertEqual(certificate.member, "Administrator") frappe.db.delete("LMS Certificate", certificate.name)
self.assertEqual(certificate.course, course.name) frappe.db.delete("LMS Course", course.name)
self.assertEqual(certificate.issue_date, nowdate())
self.assertEqual(certificate.expiry_date, add_years(nowdate(), cint(course.expiry)))
frappe.db.delete("LMS Certificate", certificate.name)
frappe.db.delete("LMS Course", course.name)

View File

@@ -1,34 +1,34 @@
// Copyright (c) 2022, Frappe and contributors // Copyright (c) 2022, Frappe and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('LMS Certificate Evaluation', { frappe.ui.form.on("LMS Certificate Evaluation", {
refresh: function(frm) { refresh: function (frm) {
if (frm.doc.status == "Pass") { if (frm.doc.status == "Pass") {
frm.add_custom_button(__("Create LMS Certificate"), () => { frm.add_custom_button(__("Create LMS Certificate"), () => {
frappe.model.open_mapped_doc({ frappe.model.open_mapped_doc({
method: "lms.lms.doctype.lms_certificate_evaluation.lms_certificate_evaluation.create_lms_certificate", method: "lms.lms.doctype.lms_certificate_evaluation.lms_certificate_evaluation.create_lms_certificate",
frm: frm frm: frm,
}); });
}); });
} }
}, },
onload: function(frm) { onload: function (frm) {
frm.set_query("course", function(doc) { frm.set_query("course", function (doc) {
return { return {
filters: { filters: {
"enable_certification": true, enable_certification: true,
"grant_certificate_after": "Evaluation" grant_certificate_after: "Evaluation",
} },
}; };
}); });
frm.set_query('member', function(doc) { frm.set_query("member", function (doc) {
return { return {
filters: { filters: {
"ignore_user_type": 1, ignore_user_type: 1,
} },
}; };
}); });
} },
}); });

View File

@@ -5,14 +5,17 @@ import frappe
from frappe.model.document import Document from frappe.model.document import Document
from frappe.model.mapper import get_mapped_doc from frappe.model.mapper import get_mapped_doc
class LMSCertificateEvaluation(Document): class LMSCertificateEvaluation(Document):
pass pass
@frappe.whitelist() @frappe.whitelist()
def create_lms_certificate(source_name, target_doc=None): def create_lms_certificate(source_name, target_doc=None):
doc = get_mapped_doc("LMS Certificate Evaluation", source_name, { doc = get_mapped_doc(
"LMS Certificate Evaluation": { "LMS Certificate Evaluation",
"doctype": "LMS Certificate" source_name,
} {"LMS Certificate Evaluation": {"doctype": "LMS Certificate"}},
}, target_doc) target_doc,
return doc )
return doc

View File

@@ -1,23 +1,23 @@
// Copyright (c) 2022, Frappe and contributors // Copyright (c) 2022, Frappe and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('LMS Certificate Request', { frappe.ui.form.on("LMS Certificate Request", {
refresh: function(frm) { refresh: function (frm) {
frm.add_custom_button(__("Create LMS Certificate Evaluation"), () => { frm.add_custom_button(__("Create LMS Certificate Evaluation"), () => {
frappe.model.open_mapped_doc({ frappe.model.open_mapped_doc({
method: "lms.lms.doctype.lms_certificate_request.lms_certificate_request.create_lms_certificate_evaluation", method: "lms.lms.doctype.lms_certificate_request.lms_certificate_request.create_lms_certificate_evaluation",
frm: frm frm: frm,
}); });
}); });
}, },
onload: function(frm) { onload: function (frm) {
frm.set_query('member', function(doc) { frm.set_query("member", function (doc) {
return { return {
filters: { filters: {
"ignore_user_type": 1, ignore_user_type: 1,
} },
}; };
}); });
} },
}); });

View File

@@ -2,54 +2,63 @@
# For license information, please see license.txt # For license information, please see license.txt
import frappe import frappe
from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
from frappe.model.mapper import get_mapped_doc from frappe.model.mapper import get_mapped_doc
from frappe import _ from frappe.utils import format_date, format_time, getdate
from frappe.utils import getdate, format_date, format_time
class LMSCertificateRequest(Document): class LMSCertificateRequest(Document):
def validate(self):
self.validate_if_existing_requests()
def validate(self): def validate_if_existing_requests(self):
self.validate_if_existing_requests() existing_requests = frappe.get_all(
"LMS Certificate Request",
{"member": self.member, "course": self.course},
["date", "start_time", "course"],
)
def validate_if_existing_requests(self): for req in existing_requests:
existing_requests = frappe.get_all("LMS Certificate Request", { if req.date == getdate(self.date) and getdate() <= getdate(self.date):
"member": self.member, course_title = frappe.db.get_value("LMS Course", req.course, "title")
"course": self.course frappe.throw(
}, ["date", "start_time", "course"]) _("You already have an evaluation on {0} at {1} for the course {2}.").format(
format_date(req.date, "medium"),
format_time(req.start_time, "short"),
course_title,
)
)
for req in existing_requests:
if req.date == getdate(self.date) and getdate() <= getdate(self.date):
course_title = frappe.db.get_value("LMS Course", req.course, "title")
frappe.throw(_(f"You already have an evaluation on {format_date(req.date, 'medium')} at {format_time(req.start_time, 'short')} for the course {course_title}."))
@frappe.whitelist() @frappe.whitelist()
def create_certificate_request(course, date, day, start_time, end_time): def create_certificate_request(course, date, day, start_time, end_time):
is_member = frappe.db.exists({ is_member = frappe.db.exists(
"doctype": "LMS Batch Membership", {"doctype": "LMS Batch Membership", "course": course, "member": frappe.session.user}
"course": course, )
"member": frappe.session.user
})
if not is_member: if not is_member:
return return
frappe.get_doc({ frappe.get_doc(
"doctype": "LMS Certificate Request", {
"course": course, "doctype": "LMS Certificate Request",
"member": frappe.session.user, "course": course,
"date": date, "member": frappe.session.user,
"day": day, "date": date,
"start_time": start_time, "day": day,
"end_time": end_time "start_time": start_time,
}).save(ignore_permissions=True) "end_time": end_time,
}
).save(ignore_permissions=True)
@frappe.whitelist() @frappe.whitelist()
def create_lms_certificate_evaluation(source_name, target_doc=None): def create_lms_certificate_evaluation(source_name, target_doc=None):
doc = get_mapped_doc("LMS Certificate Request", source_name, { doc = get_mapped_doc(
"LMS Certificate Request": { "LMS Certificate Request",
"doctype": "LMS Certificate Evaluation" source_name,
} {"LMS Certificate Request": {"doctype": "LMS Certificate Evaluation"}},
}, target_doc) target_doc,
return doc )
return doc

View File

@@ -1,33 +1,30 @@
// Copyright (c) 2021, FOSS United and contributors // Copyright (c) 2021, FOSS United and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('LMS Course', { frappe.ui.form.on("LMS Course", {
onload: function (frm) {
frm.set_query("chapter", "chapters", function () {
return {
filters: {
course: frm.doc.name,
},
};
});
onload: function (frm) { frm.set_query("instructor", "instructors", function () {
return {
filters: {
ignore_user_type: 1,
},
};
});
frm.set_query("chapter", "chapters", function () { frm.set_query("course", "related_courses", function () {
return { return {
filters: { filters: {
"course": frm.doc.name, published: true,
} },
}; };
}); });
},
frm.set_query("instructor", "instructors", function () {
return {
filters: {
"ignore_user_type": 1,
}
};
});
frm.set_query("course", "related_courses", function () {
return {
filters: {
"published": true,
}
};
});
}
}); });

View File

@@ -1,307 +1,313 @@
# Copyright (c) 2021, Frappe and contributors # Copyright (c) 2021, Frappe and contributors
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals import json
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
import json
from ...utils import generate_slug, validate_image
from frappe.utils import cint from frappe.utils import cint
from lms.lms.utils import get_chapters from lms.lms.utils import get_chapters
from ...utils import generate_slug, validate_image
class LMSCourse(Document): class LMSCourse(Document):
def validate(self):
self.validate_instructors()
self.validate_status()
self.image = validate_image(self.image)
def validate_instructors(self):
if self.is_new() and not self.instructors:
frappe.get_doc(
{
"doctype": "Course Instructor",
"instructor": self.owner,
"parent": self.name,
"parentfield": "instructors",
"parenttype": "LMS Course",
}
).save(ignore_permissions=True)
def validate(self): def validate_status(self):
self.validate_instructors() if self.published:
self.validate_status() self.status = "Approved"
self.image = validate_image(self.image)
def on_update(self):
if not self.upcoming and self.has_value_changed("upcoming"):
self.send_email_to_interested_users()
def validate_instructors(self): def send_email_to_interested_users(self):
if self.is_new() and not self.instructors: interested_users = frappe.get_all(
frappe.get_doc({ "LMS Course Interest", {"course": self.name}, ["name", "user"]
"doctype": "Course Instructor", )
"instructor": self.owner, subject = self.title + " is available!"
"parent": self.name, args = {
"parentfield": "instructors", "title": self.title,
"parenttype": "LMS Course" "course_link": f"/courses/{self.name}",
}).save(ignore_permissions=True) "app_name": frappe.db.get_single_value("System Settings", "app_name"),
"site_url": frappe.utils.get_url(),
}
for user in interested_users:
args["first_name"] = frappe.db.get_value("User", user.user, "first_name")
email_args = frappe._dict(
recipients=user.user,
subject=subject,
header=[subject, "green"],
template="lms_course_interest",
args=args,
now=True,
)
frappe.enqueue(
method=frappe.sendmail, queue="short", timeout=300, is_async=True, **email_args
)
frappe.db.set_value("LMS Course Interest", user.name, "email_sent", True)
def validate_status(self): def autoname(self):
if self.published: if not self.name:
self.status = "Approved" self.name = generate_slug(self.title, "LMS Course")
def __repr__(self):
return f"<Course#{self.name}>"
def on_update(self): def has_mentor(self, email):
if not self.upcoming and self.has_value_changed("upcoming"): """Checks if this course has a mentor with given email."""
self.send_email_to_interested_users() if not email or email == "Guest":
return False
mapping = frappe.get_all(
"LMS Course Mentor Mapping", {"course": self.name, "mentor": email}
)
return mapping != []
def send_email_to_interested_users(self): def add_mentor(self, email):
interested_users = frappe.get_all("LMS Course Interest", { """Adds a new mentor to the course."""
"course": self.name if not email:
}, raise ValueError("Invalid email")
["name", "user"]) if email == "Guest":
subject = self.title + " is available!" raise ValueError("Guest user can not be added as a mentor")
args = {
"title": self.title,
"course_link": "/courses/{0}".format(self.name),
"app_name": frappe.db.get_single_value("System Settings", "app_name"),
"site_url": frappe.utils.get_url()
}
for user in interested_users: # given user is already a mentor
args["first_name"] = frappe.db.get_value("User", user.user, "first_name") if self.has_mentor(email):
email_args = frappe._dict( return
recipients = user.user,
subject = subject,
header = [subject, "green"],
template = "lms_course_interest",
args = args,
now = True)
frappe.enqueue(method=frappe.sendmail, queue='short', timeout=300, is_async=True, **email_args)
frappe.db.set_value("LMS Course Interest", user.name, "email_sent", True)
doc = frappe.get_doc(
{"doctype": "LMS Course Mentor Mapping", "course": self.name, "mentor": email}
)
doc.insert()
def autoname(self): def get_student_batch(self, email):
if not self.name: """Returns the batch the given student is part of.
self.name = generate_slug(self.title, "LMS Course")
Returns None if the student is not part of any batch.
"""
if not email:
return
def __repr__(self): batch_name = frappe.get_value(
return f"<Course#{self.name}>" doctype="LMS Batch Membership",
filters={"course": self.name, "member_type": "Student", "member": email},
fieldname="batch",
)
return batch_name and frappe.get_doc("LMS Batch", batch_name)
def get_batches(self, mentor=None):
batches = frappe.get_all("LMS Batch", {"course": self.name})
if mentor:
# TODO: optimize this
memberships = frappe.db.get_all(
"LMS Batch Membership", {"member": mentor}, ["batch"]
)
batch_names = {m.batch for m in memberships}
return [b for b in batches if b.name in batch_names]
def has_mentor(self, email): def get_cohorts(self):
"""Checks if this course has a mentor with given email. return frappe.get_all(
""" "Cohort",
if not email or email == "Guest": {"course": self.name},
return False ["name", "slug", "title", "begin_date", "end_date"],
order_by="creation",
)
mapping = frappe.get_all("LMS Course Mentor Mapping", {"course": self.name, "mentor": email}) def get_cohort(self, cohort_slug):
return mapping != [] name = frappe.get_value("Cohort", {"course": self.name, "slug": cohort_slug})
return name and frappe.get_doc("Cohort", name)
def reindex_exercises(self):
for i, c in enumerate(get_chapters(self.name), start=1):
self._reindex_exercises_in_chapter(c, i)
def add_mentor(self, email): def _reindex_exercises_in_chapter(self, c, index):
"""Adds a new mentor to the course. i = 1
""" for lesson in self.get_lessons(c):
if not email: for exercise in lesson.get_exercises():
raise ValueError("Invalid email") exercise.index_ = i
if email == "Guest": exercise.index_label = f"{index}.{i}"
raise ValueError("Guest user can not be added as a mentor") exercise.save()
i += 1
# given user is already a mentor def get_all_memberships(self, member):
if self.has_mentor(email): all_memberships = frappe.get_all(
return "LMS Batch Membership", {"member": member, "course": self.name}, ["batch"]
)
doc = frappe.get_doc({ for membership in all_memberships:
"doctype": "LMS Course Mentor Mapping", membership.batch_title = frappe.db.get_value("LMS Batch", membership.batch, "title")
"course": self.name, return all_memberships
"mentor": email
})
doc.insert()
def get_student_batch(self, email):
"""Returns the batch the given student is part of.
Returns None if the student is not part of any batch.
"""
if not email:
return
batch_name = frappe.get_value(
doctype="LMS Batch Membership",
filters={
"course": self.name,
"member_type": "Student",
"member": email
},
fieldname="batch")
return batch_name and frappe.get_doc("LMS Batch", batch_name)
def get_batches(self, mentor=None):
batches = frappe.get_all("LMS Batch", {"course": self.name})
if mentor:
# TODO: optimize this
memberships = frappe.db.get_all(
"LMS Batch Membership",
{"member": mentor},
["batch"])
batch_names = {m.batch for m in memberships}
return [b for b in batches if b.name in batch_names]
def get_cohorts(self):
return frappe.get_all("Cohort",
{"course": self.name},
["name", "slug", "title", "begin_date", "end_date"],
order_by="creation")
def get_cohort(self, cohort_slug):
name = frappe.get_value("Cohort", {"course": self.name, "slug": cohort_slug})
return name and frappe.get_doc("Cohort", name)
def reindex_exercises(self):
for i, c in enumerate(get_chapters(self.name), start=1):
self._reindex_exercises_in_chapter(c, i)
def _reindex_exercises_in_chapter(self, c, index):
i = 1
for lesson in self.get_lessons(c):
for exercise in lesson.get_exercises():
exercise.index_ = i
exercise.index_label = f"{index}.{i}"
exercise.save()
i += 1
def get_all_memberships(self, member):
all_memberships = frappe.get_all("LMS Batch Membership", {"member": member, "course": self.name}, ["batch"])
for membership in all_memberships:
membership.batch_title = frappe.db.get_value("LMS Batch", membership.batch, "title")
return all_memberships
@frappe.whitelist() @frappe.whitelist()
def reindex_exercises(doc): def reindex_exercises(doc):
course_data = json.loads(doc) course_data = json.loads(doc)
course = frappe.get_doc("LMS Course", course_data['name']) course = frappe.get_doc("LMS Course", course_data["name"])
course.reindex_exercises() course.reindex_exercises()
frappe.msgprint("All exercises in this course have been re-indexed.") frappe.msgprint("All exercises in this course have been re-indexed.")
@frappe.whitelist(allow_guest=True) @frappe.whitelist(allow_guest=True)
def search_course(text): def search_course(text):
search_courses = [] search_courses = []
courses = frappe.get_all("LMS Course", courses = frappe.get_all(
filters= { "LMS Course",
"published": True filters={"published": True},
}, or_filters={
or_filters = { "title": ["like", f"%{text}%"],
"title": ["like", "%{0}%".format(text)], "tags": ["like", f"%{text}%"],
"tags": ["like", "%{0}%".format(text)], "short_introduction": ["like", f"%{text}%"],
"short_introduction": ["like", "%{0}%".format(text)], "description": ["like", f"%{text}%"],
"description": ["like", "%{0}%".format(text)], },
}) )
""" for course in courses: """ for course in courses:
search_courses.append(frappe.get_doc("LMS Course", course)) """ search_courses.append(frappe.get_doc("LMS Course", course)) """
""" template = frappe.render_template("lms/templates/course_list.html", { """ template = frappe.render_template("lms/templates/course_list.html", {
"title": _("Search Results"), "title": _("Search Results"),
"courses": search_courses, "courses": search_courses,
"widgets": Widgets() "widgets": Widgets()
}) """ }) """
return courses return courses
@frappe.whitelist() @frappe.whitelist()
def submit_for_review(course): def submit_for_review(course):
chapters = frappe.get_all("Chapter Reference", {"parent": course}) chapters = frappe.get_all("Chapter Reference", {"parent": course})
if not len(chapters): if not len(chapters):
return "No Chp" return "No Chp"
frappe.db.set_value("LMS Course", course, "status", "Under Review") frappe.db.set_value("LMS Course", course, "status", "Under Review")
return "OK" return "OK"
@frappe.whitelist() @frappe.whitelist()
def save_course(tags, title, short_introduction, video_link, description, course, published, upcoming, image=None): def save_course(
if course: tags,
doc = frappe.get_doc("LMS Course", course) title,
else: short_introduction,
doc = frappe.get_doc({ video_link,
"doctype": "LMS Course" description,
}) course,
published,
upcoming,
image=None,
):
if course:
doc = frappe.get_doc("LMS Course", course)
else:
doc = frappe.get_doc({"doctype": "LMS Course"})
doc.update({ doc.update(
"title": title, {
"short_introduction": short_introduction, "title": title,
"video_link": video_link, "short_introduction": short_introduction,
"image": image, "video_link": video_link,
"description": description, "image": image,
"tags": tags, "description": description,
"published": cint(published), "tags": tags,
"upcoming": cint(upcoming) "published": cint(published),
}) "upcoming": cint(upcoming),
doc.save(ignore_permissions=True) }
return doc.name )
doc.save(ignore_permissions=True)
return doc.name
@frappe.whitelist() @frappe.whitelist()
def save_chapter(course, title, chapter_description, idx, chapter): def save_chapter(course, title, chapter_description, idx, chapter):
if chapter: if chapter:
doc = frappe.get_doc("Course Chapter", chapter) doc = frappe.get_doc("Course Chapter", chapter)
else: else:
doc = frappe.get_doc({ doc = frappe.get_doc({"doctype": "Course Chapter"})
"doctype": "Course Chapter"
})
doc.update({ doc.update({"course": course, "title": title, "description": chapter_description})
"course": course, doc.save(ignore_permissions=True)
"title": title,
"description": chapter_description
})
doc.save(ignore_permissions=True)
if chapter: if chapter:
chapter_reference = frappe.get_doc("Chapter Reference", {"chapter": chapter}) chapter_reference = frappe.get_doc("Chapter Reference", {"chapter": chapter})
else: else:
chapter_reference = frappe.get_doc({ chapter_reference = frappe.get_doc(
"doctype": "Chapter Reference", {
"parent": course, "doctype": "Chapter Reference",
"parenttype": "LMS Course", "parent": course,
"parentfield": "chapters", "parenttype": "LMS Course",
"idx": idx "parentfield": "chapters",
}) "idx": idx,
}
)
chapter_reference.update({"chapter": doc.name}) chapter_reference.update({"chapter": doc.name})
chapter_reference.save(ignore_permissions=True) chapter_reference.save(ignore_permissions=True)
return doc.name return doc.name
@frappe.whitelist() @frappe.whitelist()
def save_lesson(title, body, chapter, preview, idx, lesson, youtube=None, quiz_id=None, question=None, file_type=None): def save_lesson(
if lesson: title,
doc = frappe.get_doc("Course Lesson", lesson) body,
else: chapter,
doc = frappe.get_doc({ preview,
"doctype": "Course Lesson" idx,
}) lesson,
youtube=None,
quiz_id=None,
question=None,
file_type=None,
):
if lesson:
doc = frappe.get_doc("Course Lesson", lesson)
else:
doc = frappe.get_doc({"doctype": "Course Lesson"})
doc.update({ doc.update(
"chapter": chapter, {
"title": title, "chapter": chapter,
"body": body, "title": title,
"include_in_preview": preview, "body": body,
"youtube": youtube, "include_in_preview": preview,
"quiz_id": quiz_id, "youtube": youtube,
"question": question, "quiz_id": quiz_id,
"file_type": file_type "question": question,
}) "file_type": file_type,
doc.save(ignore_permissions=True) }
)
doc.save(ignore_permissions=True)
if lesson: if lesson:
lesson_reference = frappe.get_doc("Lesson Reference", {"lesson": lesson}) lesson_reference = frappe.get_doc("Lesson Reference", {"lesson": lesson})
else: else:
lesson_reference = frappe.get_doc({ lesson_reference = frappe.get_doc(
"doctype": "Lesson Reference", {
"parent": chapter, "doctype": "Lesson Reference",
"parenttype": "Course Chapter", "parent": chapter,
"parentfield": "lessons", "parenttype": "Course Chapter",
"idx": idx "parentfield": "lessons",
}) "idx": idx,
}
)
lesson_reference.update({"lesson": doc.name}) lesson_reference.update({"lesson": doc.name})
lesson_reference.save(ignore_permissions=True) lesson_reference.save(ignore_permissions=True)
return doc.name return doc.name

View File

@@ -1,93 +1,90 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021, FOSS United and Contributors # Copyright (c) 2021, FOSS United and Contributors
# See license.txt # See license.txt
from __future__ import unicode_literals
import unittest
import frappe import frappe
from .lms_course import LMSCourse from .lms_course import LMSCourse
import unittest
class TestLMSCourse(unittest.TestCase): class TestLMSCourse(unittest.TestCase):
def test_new_course(self):
course = new_course("Test Course")
assert course.title == "Test Course"
assert course.name == "test-course"
# disabled this test as it is failing
def _test_add_mentors(self):
course = new_course("Test Course")
assert course.get_mentors() == []
def test_new_course(self): user = new_user("Tester", "tester@example.com")
course = new_course("Test Course") course.add_mentor("tester@example.com")
assert course.title == "Test Course"
assert course.name == "test-course"
mentors = course.get_mentors()
mentors_data = [
dict(email=mentor.email, batch_count=mentor.batch_count) for mentor in mentors
]
assert mentors_data == [{"email": "tester@example.com", "batch_count": 0}]
# disabled this test as it is failing def tearDown(self):
def _test_add_mentors(self): if frappe.db.exists("User", "tester@example.com"):
course = new_course("Test Course") frappe.delete_doc("User", "tester@example.com")
assert course.get_mentors() == []
user = new_user("Tester", "tester@example.com") if frappe.db.exists("LMS Course", "test-course"):
course.add_mentor("tester@example.com") frappe.db.delete("Exercise Submission", {"course": "test-course"})
frappe.db.delete("Exercise Latest Submission", {"course": "test-course"})
mentors = course.get_mentors() frappe.db.delete("Exercise", {"course": "test-course"})
mentors_data = [dict(email=mentor.email, batch_count=mentor.batch_count) for mentor in mentors] frappe.db.delete("LMS Batch Membership", {"course": "test-course"})
assert mentors_data == [{"email": "tester@example.com", "batch_count": 0}] frappe.db.delete("LMS Batch", {"course": "test-course"})
frappe.db.delete("LMS Course Mentor Mapping", {"course": "test-course"})
frappe.db.delete("Course Instructor", {"parent": "test-course"})
def tearDown(self): frappe.db.sql("delete from `tabCourse Instructor`")
if frappe.db.exists("User", "tester@example.com"): frappe.delete_doc("LMS Course", "test-course")
frappe.delete_doc("User", "tester@example.com")
if frappe.db.exists("LMS Course", "test-course"):
frappe.db.delete("Exercise Submission", {"course": "test-course"})
frappe.db.delete("Exercise Latest Submission", {"course": "test-course"})
frappe.db.delete("Exercise", {"course": "test-course"})
frappe.db.delete("LMS Batch Membership", {"course": "test-course"})
frappe.db.delete("LMS Batch", {"course": "test-course"})
frappe.db.delete("LMS Course Mentor Mapping", {"course": "test-course"})
frappe.db.delete("Course Instructor", {"parent": "test-course"})
frappe.db.sql('delete from `tabCourse Instructor`')
frappe.delete_doc("LMS Course", "test-course")
def new_user(name, email): def new_user(name, email):
user = frappe.db.exists("User", email) user = frappe.db.exists("User", email)
if user: if user:
return frappe.get_doc("User", user) return frappe.get_doc("User", user)
else: else:
filters = { filters = {
"doctype": "User", "doctype": "User",
"email": email, "email": email,
"first_name": name, "first_name": name,
"send_welcome_email": False "send_welcome_email": False,
} }
doc = frappe.get_doc(filters) doc = frappe.get_doc(filters)
doc.insert() doc.insert()
return doc return doc
def new_course(title, additional_filters=None): def new_course(title, additional_filters=None):
course = frappe.db.exists("LMS Course", { "title": title }) course = frappe.db.exists("LMS Course", {"title": title})
if course: if course:
return frappe.get_doc("LMS Course", course) return frappe.get_doc("LMS Course", course)
else: else:
create_evaluator() create_evaluator()
filters = { filters = {
"doctype": "LMS Course", "doctype": "LMS Course",
"title": title, "title": title,
"short_introduction": title, "short_introduction": title,
"description": title "description": title,
} }
if additional_filters: if additional_filters:
filters.update(additional_filters) filters.update(additional_filters)
doc = frappe.get_doc(filters) doc = frappe.get_doc(filters)
doc.insert(ignore_permissions=True) doc.insert(ignore_permissions=True)
return doc return doc
def create_evaluator(): def create_evaluator():
if not frappe.db.exists("Course Evaluator", "evaluator@example.com"): if not frappe.db.exists("Course Evaluator", "evaluator@example.com"):
new_user("Evaluator", "evaluator@example.com") new_user("Evaluator", "evaluator@example.com")
frappe.get_doc({ frappe.get_doc(
"doctype": "Course Evaluator", {"doctype": "Course Evaluator", "evaluator": "evaluator@example.com"}
"evaluator": "evaluator@example.com" ).save(ignore_permissions=True)
}).save(ignore_permissions=True)

View File

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

View File

@@ -1,10 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021, FOSS United and contributors # Copyright (c) 2021, FOSS United and contributors
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals
# import frappe # import frappe
from frappe.model.document import Document from frappe.model.document import Document
class LMSCourseEnrollment(Document): class LMSCourseEnrollment(Document):
pass pass

View File

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

View File

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

View File

@@ -4,16 +4,18 @@
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
class LMSCourseInterest(Document): class LMSCourseInterest(Document):
pass pass
@frappe.whitelist() @frappe.whitelist()
def capture_interest(course): def capture_interest(course):
data = { data = {
"doctype": "LMS Course Interest", "doctype": "LMS Course Interest",
"course": course, "course": course,
"user": frappe.session.user "user": frappe.session.user,
} }
if not frappe.db.exists(data): if not frappe.db.exists(data):
frappe.get_doc(data).save(ignore_permissions=True) frappe.get_doc(data).save(ignore_permissions=True)
return "OK" return "OK"

View File

@@ -4,5 +4,6 @@
# import frappe # import frappe
import unittest import unittest
class TestLMSCourseInterest(unittest.TestCase): class TestLMSCourseInterest(unittest.TestCase):
pass pass

View File

@@ -1,13 +1,13 @@
// Copyright (c) 2021, FOSS United and contributors // Copyright (c) 2021, FOSS United and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('LMS Course Mentor Mapping', { frappe.ui.form.on("LMS Course Mentor Mapping", {
onload: function(frm) { onload: function (frm) {
frm.set_query('mentor', function(doc) { frm.set_query("mentor", function (doc) {
return { return {
filters: { filters: {
"ignore_user_type": 1, ignore_user_type: 1,
} },
}; };
}); });
}, },

View File

@@ -1,18 +1,17 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021, FOSS United and contributors # Copyright (c) 2021, FOSS United and contributors
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals
import frappe import frappe
from frappe.model.document import Document
from frappe import _ from frappe import _
from frappe.model.document import Document
class LMSCourseMentorMapping(Document): class LMSCourseMentorMapping(Document):
def validate(self): def validate(self):
duplicate_mapping = frappe.get_all("LMS Course Mentor Mapping", duplicate_mapping = frappe.get_all(
filters = { "LMS Course Mentor Mapping", filters={"course": self.course, "mentor": self.mentor}
"course": self.course, )
"mentor": self.mentor
})
if len(duplicate_mapping): if len(duplicate_mapping):
frappe.throw(_("{0} is already a mentor for course {1}").format(self.mentor_name, self.course)) frappe.throw(
_("{0} is already a mentor for course {1}").format(self.mentor_name, self.course)
)

View File

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

View File

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

View File

@@ -4,5 +4,6 @@
# import frappe # import frappe
from frappe.model.document import Document from frappe.model.document import Document
class LMSCourseProgress(Document): class LMSCourseProgress(Document):
pass pass

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