From b9db14ad4463ee1173dd71e4910a4896cff0dd58 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Fri, 4 Nov 2022 11:14:37 +0530 Subject: [PATCH 01/10] ci: semgrep --- .github/workflows/linters.yml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/workflows/linters.yml diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml new file mode 100644 index 00000000..e6d7584c --- /dev/null +++ b/.github/workflows/linters.yml @@ -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 From 893fac348deb434659fdadd5c62e5c1b667a2c76 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Fri, 4 Nov 2022 11:22:52 +0530 Subject: [PATCH 02/10] ci: pre commit config --- .pre-commit-config.yml | 67 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 .pre-commit-config.yml diff --git a/.pre-commit-config.yml b/.pre-commit-config.yml new file mode 100644 index 00000000..2dd114d2 --- /dev/null +++ b/.pre-commit-config.yml @@ -0,0 +1,67 @@ +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: check-yaml + - 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://github.com/timothycrosley/isort + rev: 5.9.1 + hooks: + - id: isort + + - 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 From e73d59d538ebea63b2ebe86dffdb1638be99c39a Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Fri, 4 Nov 2022 11:25:35 +0530 Subject: [PATCH 03/10] ci: pre commit yaml --- .pre-commit-config.yml => .pre-commit-config.yaml | 1 - 1 file changed, 1 deletion(-) rename .pre-commit-config.yml => .pre-commit-config.yaml (98%) diff --git a/.pre-commit-config.yml b/.pre-commit-config.yaml similarity index 98% rename from .pre-commit-config.yml rename to .pre-commit-config.yaml index 2dd114d2..a0f14524 100644 --- a/.pre-commit-config.yml +++ b/.pre-commit-config.yaml @@ -17,7 +17,6 @@ repos: - id: check-ast - id: check-json - id: check-toml - - id: check-yaml - id: debug-statements - repo: https://github.com/asottile/pyupgrade From cda26ab2482c9d32dd27ad63d39b0c784391cade Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Fri, 4 Nov 2022 11:37:59 +0530 Subject: [PATCH 04/10] ci: test pre commit --- .pre-commit-config.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a0f14524..c79c736f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,6 @@ exclude: 'node_modules|.git' default_stages: [commit] fail_fast: false - repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.3.0 From 603eddf8789e519a4292bfc4ac911bb33c81e5ec Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Fri, 4 Nov 2022 11:47:09 +0530 Subject: [PATCH 05/10] fix: pre commit issues --- lms/__init__.py | 5 +- lms/config/desktop.py | 5 +- lms/config/docs.py | 1 + lms/hooks.py | 215 +- lms/install.py | 105 +- .../job_opportunity/job_opportunity.js | 7 +- .../job_opportunity/job_opportunity.py | 54 +- .../job_opportunity/test_job_opportunity.py | 1 + lms/job/doctype/job_settings/job_settings.js | 7 +- lms/job/doctype/job_settings/job_settings.py | 1 + .../doctype/job_settings/test_job_settings.py | 1 + .../new_job_alert/new_job_alert.py | 1 + .../job_opportunity/job_opportunity.js | 14 +- .../job_opportunity/job_opportunity.py | 1 + lms/lms/api.py | 226 +- .../doctype/certification/certification.js | 7 +- .../doctype/certification/certification.py | 1 + .../certification/test_certification.py | 1 + .../chapter_reference/chapter_reference.py | 1 + lms/lms/doctype/cohort/cohort.js | 7 +- lms/lms/doctype/cohort/cohort.py | 124 +- lms/lms/doctype/cohort/test_cohort.py | 1 + .../cohort_join_request.js | 7 +- .../cohort_join_request.py | 83 +- .../test_cohort_join_request.py | 1 + .../doctype/cohort_mentor/cohort_mentor.js | 7 +- .../doctype/cohort_mentor/cohort_mentor.py | 11 +- .../cohort_mentor/test_cohort_mentor.py | 1 + lms/lms/doctype/cohort_staff/cohort_staff.js | 7 +- lms/lms/doctype/cohort_staff/cohort_staff.py | 1 + .../doctype/cohort_staff/test_cohort_staff.py | 1 + .../cohort_subgroup/cohort_subgroup.js | 7 +- .../cohort_subgroup/cohort_subgroup.py | 137 +- .../cohort_subgroup/test_cohort_subgroup.py | 1 + .../cohort_web_page/cohort_web_page.py | 5 +- .../doctype/course_chapter/course_chapter.js | 20 +- .../doctype/course_chapter/course_chapter.py | 1 + .../course_chapter/test_course_chapter.py | 1 + .../course_evaluator/course_evaluator.js | 10 +- .../course_evaluator/course_evaluator.py | 69 +- .../course_instructor/course_instructor.py | 1 + .../doctype/course_lesson/course_lesson.js | 45 +- .../doctype/course_lesson/course_lesson.py | 186 +- .../course_lesson/test_course_lesson.py | 1 + .../education_detail/education_detail.py | 1 + .../evaluator_schedule/evaluator_schedule.py | 1 + lms/lms/doctype/exercise/exercise.js | 7 +- lms/lms/doctype/exercise/exercise.py | 75 +- lms/lms/doctype/exercise/test_exercise.py | 82 +- .../exercise_latest_submission.js | 7 +- .../exercise_latest_submission.py | 1 + .../test_exercise_latest_submission.py | 1 + .../exercise_submission.js | 7 +- .../exercise_submission.py | 39 +- .../test_exercise_submission.py | 1 + lms/lms/doctype/function/function.js | 7 +- lms/lms/doctype/function/function.py | 1 + lms/lms/doctype/function/test_function.py | 1 + lms/lms/doctype/industry/industry.js | 7 +- lms/lms/doctype/industry/industry.py | 1 + lms/lms/doctype/industry/test_industry.py | 1 + .../doctype/invite_request/invite_request.js | 7 +- .../doctype/invite_request/invite_request.py | 128 +- .../invite_request/test_invite_request.py | 120 +- .../lesson_assignment/lesson_assignment.js | 7 +- .../lesson_assignment/lesson_assignment.py | 67 +- .../test_lesson_assignment.py | 1 + .../lesson_reference/lesson_reference.py | 1 + lms/lms/doctype/lms_batch/lms_batch.js | 7 +- lms/lms/doctype/lms_batch/lms_batch.py | 141 +- lms/lms/doctype/lms_batch/test_lms_batch.py | 3 +- .../lms_batch_membership.js | 20 +- .../lms_batch_membership.py | 130 +- .../test_lms_batch_membership.py | 97 +- .../lms_certificate/lms_certificate.js | 20 +- .../lms_certificate/lms_certificate.py | 70 +- .../lms_certificate/test_lms_certificate.py | 31 +- .../lms_certificate_evaluation.js | 24 +- .../lms_certificate_evaluation.py | 17 +- .../lms_certificate_request.js | 16 +- .../lms_certificate_request.py | 77 +- lms/lms/doctype/lms_course/lms_course.js | 19 +- lms/lms/doctype/lms_course/lms_course.py | 480 +- lms/lms/doctype/lms_course/test_lms_course.py | 133 +- .../lms_course_enrollment.js | 7 +- .../lms_course_enrollment.py | 3 +- .../test_lms_course_enrollment.py | 3 +- .../lms_course_interest.js | 7 +- .../lms_course_interest.py | 18 +- .../test_lms_course_interest.py | 1 + .../lms_course_mentor_mapping.js | 20 +- .../lms_course_mentor_mapping.py | 17 +- .../test_lms_course_mentor_mapping.py | 3 +- .../lms_course_progress.js | 7 +- .../lms_course_progress.py | 1 + .../test_lms_course_progress.py | 1 + .../lms_course_review/lms_course_review.js | 7 +- .../lms_course_review/lms_course_review.py | 26 +- .../test_lms_course_review.py | 1 + .../lms_mentor_request/lms_mentor_request.js | 7 +- .../lms_mentor_request/lms_mentor_request.py | 211 +- .../test_lms_mentor_request.py | 3 +- lms/lms/doctype/lms_option/lms_option.py | 1 + lms/lms/doctype/lms_quiz/lms_quiz.js | 7 +- lms/lms/doctype/lms_quiz/lms_quiz.py | 192 +- lms/lms/doctype/lms_quiz/test_lms_quiz.py | 66 +- .../lms_quiz_question/lms_quiz_question.py | 1 + .../lms_quiz_result/lms_quiz_result.py | 1 + .../lms_quiz_submission.js | 7 +- .../lms_quiz_submission.py | 1 + .../test_lms_quiz_submission.py | 1 + lms/lms/doctype/lms_section/lms_section.py | 51 +- lms/lms/doctype/lms_settings/lms_settings.js | 7 +- lms/lms/doctype/lms_settings/lms_settings.py | 8 +- .../doctype/lms_settings/test_lms_settings.py | 3 +- .../preferred_function/preferred_function.py | 1 + .../preferred_industry/preferred_industry.js | 7 +- .../preferred_industry/preferred_industry.py | 1 + .../test_preferred_industry.py | 1 + .../related_courses/related_courses.js | 7 +- .../related_courses/related_courses.py | 1 + .../related_courses/test_related_courses.py | 1 + lms/lms/doctype/skill/skill.js | 7 +- lms/lms/doctype/skill/skill.py | 1 + lms/lms/doctype/skill/test_skill.py | 1 + lms/lms/doctype/skills/skills.js | 7 +- lms/lms/doctype/skills/skills.py | 1 + lms/lms/doctype/skills/test_skills.py | 1 + .../work_experience/work_experience.py | 1 + lms/lms/md.py | 153 +- lms/lms/models.py | 3 +- .../certificate_request_creation.py | 1 + .../certificate_request_reminder.py | 1 + .../course_progress_summary.js | 18 +- .../course_progress_summary.py | 197 +- lms/lms/test_utils.py | 114 +- lms/lms/utils.py | 865 +- .../add_a_new_batch/add_a_new_batch.js | 74 +- .../add_a_new_batch/add_a_new_batch.py | 7 +- lms/lms/web_form/profile/profile.js | 133 +- lms/lms/web_form/profile/profile.py | 1 + lms/overrides/test_user.py | 60 +- lms/overrides/user.py | 549 +- lms/overrides/web_template.py | 26 +- lms/page_renderers.py | 131 +- .../change_name_for_community_members.py | 12 +- .../create_mentor_request_email_templates.py | 68 +- ...ce_member_with_user_in_batch_membership.py | 12 +- ...mber_with_user_in_course_mentor_mapping.py | 12 +- ...replace_member_with_user_in_lms_message.py | 16 +- ...lace_member_with_user_in_mentor_request.py | 16 +- .../save_abbr_for_community_members.py | 4 +- lms/patches/set_email_preferences.py | 6 +- .../v0_0/add_progress_to_membership.py | 29 +- .../v0_0/certification_member_field_data.py | 11 +- .../v0_0/change_published_field_data.py | 9 +- .../v0_0/chapter_lesson_index_table.py | 66 +- lms/patches/v0_0/course_instructor_update.py | 9 +- .../v0_0/create_course_instructor_role.py | 5 +- .../v0_0/create_course_moderator_role.py | 6 +- lms/patches/v0_0/delete_course_web_forms.py | 7 +- lms/patches/v0_0/delete_old_module_docs.py | 19 +- .../v0_0/modify_installed_apps_list.py | 10 +- .../v0_0/move_certification_to_certificate.py | 27 +- lms/patches/v0_0/multiple_instructors.py | 27 +- lms/patches/v0_0/quiz_submission_member.py | 13 +- .../v0_0/rename_chapter_and_lesson_doctype.py | 78 +- .../rename_chapters_and_lessons_doctype.py | 47 +- lms/patches/v0_0/rename_school_to_lms.py | 10 +- lms/patches/v0_0/set_course_in_lesson.py | 11 +- lms/patches/v0_0/set_courses_page_as_home.py | 7 +- lms/patches/v0_0/set_dashboard.py | 3 +- lms/patches/v0_0/set_status_in_course.py | 13 +- lms/plugins.py | 201 +- lms/public/js/common_functions.js | 89 +- lms/public/js/html2canvas.js | 8243 ++++++++++++----- lms/public/js/livecode-canvas.js | 109 +- lms/public/js/profile.js | 14 +- lms/public/js/website.bundle.js | 6 +- lms/routing.py | 31 +- lms/subscription_utils.py | 51 +- lms/templates/search_course/search_course.js | 106 +- lms/test_widgets.py | 22 +- lms/widgets.py | 75 +- lms/www/batch/join.py | 13 +- lms/www/batch/learn.js | 224 +- lms/www/batch/learn.py | 141 +- lms/www/batch/quiz.js | 83 +- lms/www/batch/quiz.py | 31 +- lms/www/batch/quiz_list.py | 7 +- lms/www/cohorts/cohort.py | 50 +- lms/www/cohorts/index.py | 53 +- lms/www/cohorts/join.py | 34 +- lms/www/cohorts/subgroup.py | 104 +- lms/www/cohorts/utils.py | 34 +- lms/www/community/index.js | 88 +- lms/www/community/index.py | 17 +- lms/www/courses/certificate.js | 47 +- lms/www/courses/certificate.py | 53 +- lms/www/courses/course.js | 194 +- lms/www/courses/course.py | 142 +- lms/www/courses/index.js | 28 +- lms/www/courses/index.py | 64 +- lms/www/jobs/index.py | 23 +- lms/www/jobs/job.js | 47 +- lms/www/jobs/job.py | 13 +- lms/www/profiles/profile.js | 26 +- lms/www/profiles/profile.py | 64 +- lms/www/utils.py | 56 +- setup.py | 17 +- 210 files changed, 10725 insertions(+), 6733 deletions(-) diff --git a/lms/__init__.py b/lms/__init__.py index 3dad4549..f102a9ca 100644 --- a/lms/__init__.py +++ b/lms/__init__.py @@ -1,4 +1 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -__version__ = '0.0.1' +__version__ = "0.0.1" diff --git a/lms/config/desktop.py b/lms/config/desktop.py index e3c71c5c..94ae99e0 100644 --- a/lms/config/desktop.py +++ b/lms/config/desktop.py @@ -1,7 +1,6 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals from frappe import _ + def get_data(): return [ { @@ -9,6 +8,6 @@ def get_data(): "color": "grey", "icon": "octicon octicon-file-directory", "type": "module", - "label": _("Community") + "label": _("Community"), } ] diff --git a/lms/config/docs.py b/lms/config/docs.py index a5279849..d6446dfe 100644 --- a/lms/config/docs.py +++ b/lms/config/docs.py @@ -7,5 +7,6 @@ Configuration for docs # headline = "App that does everything" # sub_heading = "Yes, you got that right the first time, everything" + def get_context(context): context.brand_html = "Community" diff --git a/lms/hooks.py b/lms/hooks.py index 8e278561..52e30375 100644 --- a/lms/hooks.py +++ b/lms/hooks.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals from . import __version__ as app_version app_name = "frappe_lms" @@ -47,7 +45,7 @@ web_include_js = ["website.bundle.js"] # website user home page (by Role) # role_home_page = { -# "Role": "home_page" +# "Role": "home_page" # } # Generators @@ -87,8 +85,8 @@ after_uninstall = "lms.install.after_uninstall" # Override standard doctype classes override_doctype_class = { - "User": "lms.overrides.user.CustomUser", - "Web Template": "lms.overrides.web_template.CustomWebTemplate" + "User": "lms.overrides.user.CustomUser", + "Web Template": "lms.overrides.web_template.CustomWebTemplate", } # Document Events @@ -96,18 +94,16 @@ override_doctype_class = { # Hook on document methods and events doc_events = { - "Discussion Reply": { - "after_insert": "lms.lms.utils.create_notification_log" - } + "Discussion Reply": {"after_insert": "lms.lms.utils.create_notification_log"} } # Scheduled Tasks # --------------- -#scheduler_events = { -# "daily": [ -# "erpnext.stock.reorder_item.reorder_item" -# ] -#} +# scheduler_events = { +# "daily": [ +# "erpnext.stock.reorder_item.reorder_item" +# ] +# } fixtures = ["Custom Field", "Function", "Industry"] @@ -136,79 +132,94 @@ fixtures = ["Custom Field", "Function", "Industry"] # Add all simple route rules here website_route_rules = [ - {"from_route": "/sketches/", "to_route": "sketches/sketch"}, - {"from_route": "/courses/", "to_route": "courses/course"}, - {"from_route": "/courses//", "to_route": "courses/certificate"}, - {"from_route": "/courses//learn", "to_route": "batch/learn"}, - {"from_route": "/courses//learn/.", "to_route": "batch/learn"}, - {"from_route": "/quizzes", "to_route": "batch/quiz_list"}, - {"from_route": "/quizzes/", "to_route": "batch/quiz"}, - {"from_route": "/courses//progress", "to_route": "batch/progress"}, - {"from_route": "/courses//join", "to_route": "batch/join"}, - {"from_route": "/courses//manage", "to_route": "cohorts"}, - {"from_route": "/courses//cohorts/", "to_route": "cohorts/cohort"}, - {"from_route": "/courses//cohorts//", "to_route": "cohorts/cohort"}, - {"from_route": "/courses//subgroups//", "to_route": "cohorts/subgroup"}, - {"from_route": "/courses//subgroups///", "to_route": "cohorts/subgroup"}, - {"from_route": "/courses//join///", "to_route": "cohorts/join"}, - {"from_route": "/users", "to_route": "profiles/profile"}, - {"from_route": "/jobs/", "to_route": "jobs/job"} + {"from_route": "/sketches/", "to_route": "sketches/sketch"}, + {"from_route": "/courses/", "to_route": "courses/course"}, + {"from_route": "/courses//", "to_route": "courses/certificate"}, + {"from_route": "/courses//learn", "to_route": "batch/learn"}, + { + "from_route": "/courses//learn/.", + "to_route": "batch/learn", + }, + {"from_route": "/quizzes", "to_route": "batch/quiz_list"}, + {"from_route": "/quizzes/", "to_route": "batch/quiz"}, + {"from_route": "/courses//progress", "to_route": "batch/progress"}, + {"from_route": "/courses//join", "to_route": "batch/join"}, + {"from_route": "/courses//manage", "to_route": "cohorts"}, + {"from_route": "/courses//cohorts/", "to_route": "cohorts/cohort"}, + { + "from_route": "/courses//cohorts//", + "to_route": "cohorts/cohort", + }, + { + "from_route": "/courses//subgroups//", + "to_route": "cohorts/subgroup", + }, + { + "from_route": "/courses//subgroups///", + "to_route": "cohorts/subgroup", + }, + { + "from_route": "/courses//join///", + "to_route": "cohorts/join", + }, + {"from_route": "/users", "to_route": "profiles/profile"}, + {"from_route": "/jobs/", "to_route": "jobs/job"}, ] website_redirects = [ - {"source": "/update-profile", "target": "/edit-profile"}, - {"source": "/dashboard", "target": "/courses"}, + {"source": "/update-profile", "target": "/edit-profile"}, + {"source": "/dashboard", "target": "/courses"}, ] update_website_context = [ - 'lms.widgets.update_website_context', + "lms.widgets.update_website_context", ] jinja = { - "methods": [ - "lms.page_renderers.get_profile_url", - "lms.overrides.user.get_enrolled_courses", - "lms.overrides.user.get_course_membership", - "lms.overrides.user.get_authored_courses", - "lms.overrides.user.get_palette", - "lms.lms.utils.get_membership", - "lms.lms.utils.get_lessons", - "lms.lms.utils.get_tags", - "lms.lms.utils.get_instructors", - "lms.lms.utils.get_students", - "lms.lms.utils.get_average_rating", - "lms.lms.utils.is_certified", - "lms.lms.utils.get_lesson_index", - "lms.lms.utils.get_lesson_url", - "lms.lms.utils.get_chapters", - "lms.lms.utils.get_slugified_chapter_title", - "lms.lms.utils.get_progress", - "lms.lms.utils.render_html", - "lms.lms.utils.is_mentor", - "lms.lms.utils.is_cohort_staff", - "lms.lms.utils.get_mentors", - "lms.lms.utils.get_reviews", - "lms.lms.utils.is_eligible_to_review", - "lms.lms.utils.get_initial_members", - "lms.lms.utils.get_sorted_reviews", - "lms.lms.utils.is_instructor", - "lms.lms.utils.convert_number_to_character", - "lms.lms.utils.get_signup_optin_checks", - "lms.lms.utils.get_popular_courses", - "lms.lms.utils.format_amount", - "lms.lms.utils.first_lesson_exists", - "lms.lms.utils.get_courses_under_review", - "lms.lms.utils.has_course_instructor_role", - "lms.lms.utils.has_course_moderator_role", - "lms.lms.utils.get_certificates", - "lms.lms.utils.format_number", - "lms.lms.utils.get_lesson_count", - "lms.lms.utils.get_all_memberships", - "lms.lms.utils.get_filtered_membership", - "lms.lms.utils.show_start_learing_cta", - "lms.lms.utils.can_create_courses" - ], - "filters": [] + "methods": [ + "lms.page_renderers.get_profile_url", + "lms.overrides.user.get_enrolled_courses", + "lms.overrides.user.get_course_membership", + "lms.overrides.user.get_authored_courses", + "lms.overrides.user.get_palette", + "lms.lms.utils.get_membership", + "lms.lms.utils.get_lessons", + "lms.lms.utils.get_tags", + "lms.lms.utils.get_instructors", + "lms.lms.utils.get_students", + "lms.lms.utils.get_average_rating", + "lms.lms.utils.is_certified", + "lms.lms.utils.get_lesson_index", + "lms.lms.utils.get_lesson_url", + "lms.lms.utils.get_chapters", + "lms.lms.utils.get_slugified_chapter_title", + "lms.lms.utils.get_progress", + "lms.lms.utils.render_html", + "lms.lms.utils.is_mentor", + "lms.lms.utils.is_cohort_staff", + "lms.lms.utils.get_mentors", + "lms.lms.utils.get_reviews", + "lms.lms.utils.is_eligible_to_review", + "lms.lms.utils.get_initial_members", + "lms.lms.utils.get_sorted_reviews", + "lms.lms.utils.is_instructor", + "lms.lms.utils.convert_number_to_character", + "lms.lms.utils.get_signup_optin_checks", + "lms.lms.utils.get_popular_courses", + "lms.lms.utils.format_amount", + "lms.lms.utils.first_lesson_exists", + "lms.lms.utils.get_courses_under_review", + "lms.lms.utils.has_course_instructor_role", + "lms.lms.utils.has_course_moderator_role", + "lms.lms.utils.get_certificates", + "lms.lms.utils.format_number", + "lms.lms.utils.get_lesson_count", + "lms.lms.utils.get_all_memberships", + "lms.lms.utils.get_filtered_membership", + "lms.lms.utils.show_start_learing_cta", + "lms.lms.utils.can_create_courses", + ], + "filters": [], } ## Specify the additional tabs to be included in the user profile page. ## Each entry must be a subclass of lms.lms.plugins.ProfileTab @@ -219,42 +230,42 @@ jinja = { ## subclass of lms.plugins.PageExtension # lms_lesson_page_extension = None -#lms_lesson_page_extensions = [ -# "lms.plugins.LiveCodeExtension" -#] +# lms_lesson_page_extensions = [ +# "lms.plugins.LiveCodeExtension" +# ] profile_mandatory_fields = [ - "first_name", - "last_name", - "user_image", - "bio", - "linkedin", - "education", - "skill", - "preferred_functions", - "preferred_industries", - "dream_companies", - "attire", - "collaboration", - "role", - "location_preference", - "time", - "company_type" + "first_name", + "last_name", + "user_image", + "bio", + "linkedin", + "education", + "skill", + "preferred_functions", + "preferred_industries", + "dream_companies", + "attire", + "collaboration", + "role", + "location_preference", + "time", + "company_type", ] ## Markdown Macros for Lessons lms_markdown_macro_renderers = { - "Exercise": "lms.plugins.exercise_renderer", - "Quiz": "lms.plugins.quiz_renderer", - "YouTubeVideo": "lms.plugins.youtube_video_renderer", - "Video": "lms.plugins.video_renderer", - "Assignment": "lms.plugins.assignment_renderer" + "Exercise": "lms.plugins.exercise_renderer", + "Quiz": "lms.plugins.quiz_renderer", + "YouTubeVideo": "lms.plugins.youtube_video_renderer", + "Video": "lms.plugins.video_renderer", + "Assignment": "lms.plugins.assignment_renderer", } # page_renderer to manage profile pages page_renderer = [ "lms.page_renderers.ProfileRedirectPage", - "lms.page_renderers.ProfilePage" + "lms.page_renderers.ProfilePage", ] # set this to "/" to have profiles on the top-level diff --git a/lms/install.py b/lms/install.py index 417c62c2..4adf68cc 100644 --- a/lms/install.py +++ b/lms/install.py @@ -3,58 +3,91 @@ from frappe.desk.page.setup_wizard.setup_wizard import add_all_roles_to def after_sync(): - create_lms_roles() - set_default_home() - add_all_roles_to("Administrator") + create_lms_roles() + set_default_home() + add_all_roles_to("Administrator") def after_uninstall(): - delete_custom_fields() + delete_custom_fields() def create_lms_roles(): - create_instructor_role() - create_moderator_role() + create_instructor_role() + create_moderator_role() 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(): - if not frappe.db.exists("Role", "Course Instructor"): - role = frappe.get_doc({ - "doctype": "Role", - "role_name": "Course Instructor", - "home_page": "", - "desk_access": 0 - }) - role.save(ignore_permissions=True) + if not frappe.db.exists("Role", "Course Instructor"): + role = frappe.get_doc( + { + "doctype": "Role", + "role_name": "Course Instructor", + "home_page": "", + "desk_access": 0, + } + ) + role.save(ignore_permissions=True) def create_moderator_role(): - if not frappe.db.exists("Role", "Course Moderator"): - role = frappe.get_doc({ - "doctype": "Role", - "role_name": "Course Moderator", - "home_page": "", - "desk_access": 0 - }) - role.save(ignore_permissions=True) + if not frappe.db.exists("Role", "Course Moderator"): + role = frappe.get_doc( + { + "doctype": "Role", + "role_name": "Course Moderator", + "home_page": "", + "desk_access": 0, + } + ) + role.save(ignore_permissions=True) def delete_custom_fields(): - fields = [ "user_category", "headline", "college", "city", "verify_terms", "country", - "preferred_location", "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" - ] + fields = [ + "user_category", + "headline", + "college", + "city", + "verify_terms", + "country", + "preferred_location", + "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: - frappe.db.delete("Custom Field", {"fieldname": field}) - frappe.db.commit() + for field in fields: + frappe.db.delete("Custom Field", {"fieldname": field}) + frappe.db.commit() diff --git a/lms/job/doctype/job_opportunity/job_opportunity.js b/lms/job/doctype/job_opportunity/job_opportunity.js index 77f917b0..5dde275e 100644 --- a/lms/job/doctype/job_opportunity/job_opportunity.js +++ b/lms/job/doctype/job_opportunity/job_opportunity.js @@ -1,8 +1,7 @@ // Copyright (c) 2021, Frappe and contributors // For license information, please see license.txt -frappe.ui.form.on('Job Opportunity', { - // refresh: function(frm) { - - // } +frappe.ui.form.on("Job Opportunity", { + // refresh: function(frm) { + // } }); diff --git a/lms/job/doctype/job_opportunity/job_opportunity.py b/lms/job/doctype/job_opportunity/job_opportunity.py index 643638ba..5fe9f614 100644 --- a/lms/job/doctype/job_opportunity/job_opportunity.py +++ b/lms/job/doctype/job_opportunity/job_opportunity.py @@ -2,40 +2,40 @@ # For license information, please see license.txt import frappe -from frappe.model.document import Document -from frappe.utils.user import get_system_managers from frappe import _ +from frappe.model.document import Document from frappe.utils import get_link_to_form +from frappe.utils.user import get_system_managers + from lms.lms.utils import validate_image + class JobOpportunity(Document): + def validate(self): + self.validate_urls() + self.company_logo = validate_image(self.company_logo) - - def validate(self): - self.validate_urls() - 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) + def validate_urls(self): + frappe.utils.validate_url(self.company_website, True) + frappe.utils.validate_url(self.application_link, True) @frappe.whitelist() def report(job, reason): - system_managers = get_system_managers(only_name=True) - user = frappe.db.get_value("User", frappe.session.user, "full_name") - subject = _("User {0} has reported the job post {1}").format(user, job) - args = { - "job": job, - "job_url": get_link_to_form("Job Opportunity", job), - "user": user, - "reason": reason - } - frappe.sendmail( - recipients = system_managers, - subject=subject, - header=[subject, "green"], - template = "job_report", - args=args, - now=True) + system_managers = get_system_managers(only_name=True) + user = frappe.db.get_value("User", frappe.session.user, "full_name") + subject = _("User {0} has reported the job post {1}").format(user, job) + args = { + "job": job, + "job_url": get_link_to_form("Job Opportunity", job), + "user": user, + "reason": reason, + } + frappe.sendmail( + recipients=system_managers, + subject=subject, + header=[subject, "green"], + template="job_report", + args=args, + now=True, + ) diff --git a/lms/job/doctype/job_opportunity/test_job_opportunity.py b/lms/job/doctype/job_opportunity/test_job_opportunity.py index 1823b779..bd8112f0 100644 --- a/lms/job/doctype/job_opportunity/test_job_opportunity.py +++ b/lms/job/doctype/job_opportunity/test_job_opportunity.py @@ -4,5 +4,6 @@ # import frappe import unittest + class TestJobOpportunity(unittest.TestCase): pass diff --git a/lms/job/doctype/job_settings/job_settings.js b/lms/job/doctype/job_settings/job_settings.js index 7234fdf2..70f5f5cf 100644 --- a/lms/job/doctype/job_settings/job_settings.js +++ b/lms/job/doctype/job_settings/job_settings.js @@ -1,8 +1,7 @@ // Copyright (c) 2022, Frappe and contributors // For license information, please see license.txt -frappe.ui.form.on('Job Settings', { - // refresh: function(frm) { - - // } +frappe.ui.form.on("Job Settings", { + // refresh: function(frm) { + // } }); diff --git a/lms/job/doctype/job_settings/job_settings.py b/lms/job/doctype/job_settings/job_settings.py index 48d20ea9..10b2713e 100644 --- a/lms/job/doctype/job_settings/job_settings.py +++ b/lms/job/doctype/job_settings/job_settings.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class JobSettings(Document): pass diff --git a/lms/job/doctype/job_settings/test_job_settings.py b/lms/job/doctype/job_settings/test_job_settings.py index 65294592..db578782 100644 --- a/lms/job/doctype/job_settings/test_job_settings.py +++ b/lms/job/doctype/job_settings/test_job_settings.py @@ -4,5 +4,6 @@ # import frappe import unittest + class TestJobSettings(unittest.TestCase): pass diff --git a/lms/job/notification/new_job_alert/new_job_alert.py b/lms/job/notification/new_job_alert/new_job_alert.py index e1ada619..80b7b873 100644 --- a/lms/job/notification/new_job_alert/new_job_alert.py +++ b/lms/job/notification/new_job_alert/new_job_alert.py @@ -1,5 +1,6 @@ import frappe + def get_context(context): # do your magic here pass diff --git a/lms/job/web_form/job_opportunity/job_opportunity.js b/lms/job/web_form/job_opportunity/job_opportunity.js index 274a84c1..de684c41 100644 --- a/lms/job/web_form/job_opportunity/job_opportunity.js +++ b/lms/job/web_form/job_opportunity/job_opportunity.js @@ -1,7 +1,7 @@ -frappe.ready(function() { - frappe.web_form.after_save = () => { - setTimeout(() => { - window.location.href = `/jobs`; - }) - } -}) +frappe.ready(function () { + frappe.web_form.after_save = () => { + setTimeout(() => { + window.location.href = `/jobs`; + }); + }; +}); diff --git a/lms/job/web_form/job_opportunity/job_opportunity.py b/lms/job/web_form/job_opportunity/job_opportunity.py index e1ada619..80b7b873 100644 --- a/lms/job/web_form/job_opportunity/job_opportunity.py +++ b/lms/job/web_form/job_opportunity/job_opportunity.py @@ -1,5 +1,6 @@ import frappe + def get_context(context): # do your magic here pass diff --git a/lms/lms/api.py b/lms/lms/api.py index f6aee823..6e36f79e 100644 --- a/lms/lms/api.py +++ b/lms/lms/api.py @@ -3,161 +3,139 @@ import frappe + @frappe.whitelist() def autosave_section(section, code): - """Saves the code edited in one of the sections. - """ - doc = frappe.get_doc( - doctype="Code Revision", - section=section, - code=code, - author=frappe.session.user) - doc.insert() - return {"name": doc.name} + """Saves the code edited in one of the sections.""" + doc = frappe.get_doc( + doctype="Code Revision", section=section, code=code, author=frappe.session.user + ) + doc.insert() + return {"name": doc.name} + @frappe.whitelist() def submit_solution(exercise, code): - """Submits a solution. + """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() def save_current_lesson(course_name, lesson_name): - """Saves the current lesson for a student/mentor. - """ - name = frappe.get_value( - doctype="LMS Batch Membership", - filters={ - "course": course_name, - "member": frappe.session.user - }, - fieldname="name") - if not name: - return - doc = frappe.get_doc("LMS Batch Membership", name) - doc.current_lesson = lesson_name - doc.save(ignore_permissions=True) - return {"current_lesson": doc.current_lesson} + """Saves the current lesson for a student/mentor.""" + name = frappe.get_value( + doctype="LMS Batch Membership", + filters={"course": course_name, "member": frappe.session.user}, + fieldname="name", + ) + if not name: + return + doc = frappe.get_doc("LMS Batch Membership", name) + doc.current_lesson = lesson_name + doc.save(ignore_permissions=True) + return {"current_lesson": doc.current_lesson} @frappe.whitelist() def join_cohort(course, cohort, subgroup, invite_code): - """Creates a Cohort Join Request for given user. - """ - course_doc = frappe.get_doc("LMS Course", course) - cohort_doc = course_doc and course_doc.get_cohort(cohort) - subgroup_doc = cohort_doc and cohort_doc.get_subgroup(subgroup) + """Creates a Cohort Join Request for given user.""" + course_doc = frappe.get_doc("LMS Course", course) + cohort_doc = course_doc and course_doc.get_cohort(cohort) + subgroup_doc = cohort_doc and cohort_doc.get_subgroup(subgroup) - if not subgroup_doc or subgroup_doc.invite_code != invite_code: - return { - "ok": False, - "error": "Invalid join link" - } + if not subgroup_doc or subgroup_doc.invite_code != invite_code: + return {"ok": False, "error": "Invalid join link"} + + data = { + "doctype": "Cohort Join Request", + "cohort": cohort_doc.name, + "subgroup": subgroup_doc.name, + "email": frappe.session.user, + "status": "Pending", + } + # Don't insert duplicate records + if frappe.db.exists(data): + return {"ok": True, "status": "record found"} + else: + doc = frappe.get_doc(data) + doc.insert(ignore_permissions=True) + return {"ok": True, "status": "record created"} - data = { - "doctype": "Cohort Join Request", - "cohort": cohort_doc.name, - "subgroup": subgroup_doc.name, - "email": frappe.session.user, - "status": "Pending" - } - # Don't insert duplicate records - if frappe.db.exists(data): - return {"ok": True, "status": "record found"} - else: - doc = frappe.get_doc(data) - doc.insert(ignore_permissions=True) - return {"ok": True, "status": "record created"} @frappe.whitelist() def approve_cohort_join_request(join_request): - r = frappe.get_doc("Cohort Join Request", join_request) - sg = r and frappe.get_doc("Cohort Subgroup", r.subgroup) - if not sg or r.status not in ["Pending", "Accepted"]: - return { - "ok": False, - "error": "Invalid Join Request" - } - if not sg.is_manager(frappe.session.user) and "System Manager" not in frappe.get_roles(): - return { - "ok": False, - "error": "Permission Deined" - } + r = frappe.get_doc("Cohort Join Request", join_request) + sg = r and frappe.get_doc("Cohort Subgroup", r.subgroup) + if not sg or r.status not in ["Pending", "Accepted"]: + return {"ok": False, "error": "Invalid Join Request"} + if ( + not sg.is_manager(frappe.session.user) and "System Manager" not in frappe.get_roles() + ): + return {"ok": False, "error": "Permission Deined"} + + r.status = "Accepted" + r.save(ignore_permissions=True) + return {"ok": True} - r.status = "Accepted" - r.save(ignore_permissions=True) - return {"ok": True} @frappe.whitelist() def reject_cohort_join_request(join_request): - r = frappe.get_doc("Cohort Join Request", join_request) - sg = r and frappe.get_doc("Cohort Subgroup", r.subgroup) - if not sg or r.status not in ["Pending", "Rejected"]: - return { - "ok": False, - "error": "Invalid Join Request" - } - if not sg.is_manager(frappe.session.user) and "System Manager" not in frappe.get_roles(): - return { - "ok": False, - "error": "Permission Deined" - } + r = frappe.get_doc("Cohort Join Request", join_request) + sg = r and frappe.get_doc("Cohort Subgroup", r.subgroup) + if not sg or r.status not in ["Pending", "Rejected"]: + return {"ok": False, "error": "Invalid Join Request"} + if ( + not sg.is_manager(frappe.session.user) and "System Manager" not in frappe.get_roles() + ): + return {"ok": False, "error": "Permission Deined"} - r.status = "Rejected" - r.save(ignore_permissions=True) - return {"ok": True} + r.status = "Rejected" + r.save(ignore_permissions=True) + return {"ok": True} @frappe.whitelist() def undo_reject_cohort_join_request(join_request): - r = frappe.get_doc("Cohort Join Request", join_request) - sg = r and frappe.get_doc("Cohort Subgroup", r.subgroup) - # keeping Pending as well to consider the case of duplicate requests - if not sg or r.status not in ["Pending", "Rejected"]: - return { - "ok": False, - "error": "Invalid Join Request" - } - if not sg.is_manager(frappe.session.user) and "System Manager" not in frappe.get_roles(): - return { - "ok": False, - "error": "Permission Deined" - } + r = frappe.get_doc("Cohort Join Request", join_request) + sg = r and frappe.get_doc("Cohort Subgroup", r.subgroup) + # keeping Pending as well to consider the case of duplicate requests + if not sg or r.status not in ["Pending", "Rejected"]: + return {"ok": False, "error": "Invalid Join Request"} + if ( + not sg.is_manager(frappe.session.user) and "System Manager" not in frappe.get_roles() + ): + return {"ok": False, "error": "Permission Deined"} + + r.status = "Pending" + r.save(ignore_permissions=True) + return {"ok": True} - r.status = "Pending" - r.save(ignore_permissions=True) - return {"ok": True} @frappe.whitelist() def add_mentor_to_subgroup(subgroup, email): - try: - sg = frappe.get_doc("Cohort Subgroup", subgroup) - except frappe.DoesNotExistError: - return { - "ok": False, - "error": f"Invalid subgroup: {subgroup}" - } + try: + sg = frappe.get_doc("Cohort Subgroup", subgroup) + except frappe.DoesNotExistError: + return {"ok": False, "error": f"Invalid subgroup: {subgroup}"} - if not sg.get_cohort().is_admin(frappe.session.user) and "System Manager" not in frappe.get_roles(): - return { - "ok": False, - "error": "Permission Deined" - } + if ( + not sg.get_cohort().is_admin(frappe.session.user) + and "System Manager" not in frappe.get_roles() + ): + return {"ok": False, "error": "Permission Deined"} - try: - user = frappe.get_doc("User", email) - except frappe.DoesNotExistError: - return { - "ok": False, - "error": f"Invalid user: {email}" - } + try: + user = frappe.get_doc("User", email) + except frappe.DoesNotExistError: + return {"ok": False, "error": f"Invalid user: {email}"} - sg.add_mentor(email) - return {"ok": True} + sg.add_mentor(email) + return {"ok": True} diff --git a/lms/lms/doctype/certification/certification.js b/lms/lms/doctype/certification/certification.js index b61d405d..5af1a2e8 100644 --- a/lms/lms/doctype/certification/certification.js +++ b/lms/lms/doctype/certification/certification.js @@ -1,8 +1,7 @@ // Copyright (c) 2021, Frappe and contributors // For license information, please see license.txt -frappe.ui.form.on('Certification', { - // refresh: function(frm) { - - // } +frappe.ui.form.on("Certification", { + // refresh: function(frm) { + // } }); diff --git a/lms/lms/doctype/certification/certification.py b/lms/lms/doctype/certification/certification.py index 70bf6f4c..606ec386 100644 --- a/lms/lms/doctype/certification/certification.py +++ b/lms/lms/doctype/certification/certification.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class Certification(Document): pass diff --git a/lms/lms/doctype/certification/test_certification.py b/lms/lms/doctype/certification/test_certification.py index 180107f1..fbf6f901 100644 --- a/lms/lms/doctype/certification/test_certification.py +++ b/lms/lms/doctype/certification/test_certification.py @@ -4,5 +4,6 @@ # import frappe import unittest + class TestCertification(unittest.TestCase): pass diff --git a/lms/lms/doctype/chapter_reference/chapter_reference.py b/lms/lms/doctype/chapter_reference/chapter_reference.py index 73ca7b4f..72dbf802 100644 --- a/lms/lms/doctype/chapter_reference/chapter_reference.py +++ b/lms/lms/doctype/chapter_reference/chapter_reference.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class ChapterReference(Document): pass diff --git a/lms/lms/doctype/cohort/cohort.js b/lms/lms/doctype/cohort/cohort.js index 002b8b3c..211eba7f 100644 --- a/lms/lms/doctype/cohort/cohort.js +++ b/lms/lms/doctype/cohort/cohort.js @@ -1,8 +1,7 @@ // Copyright (c) 2021, FOSS United and contributors // For license information, please see license.txt -frappe.ui.form.on('Cohort', { - // refresh: function(frm) { - - // } +frappe.ui.form.on("Cohort", { + // refresh: function(frm) { + // } }); diff --git a/lms/lms/doctype/cohort/cohort.py b/lms/lms/doctype/cohort/cohort.py index 429baac5..eccc2391 100644 --- a/lms/lms/doctype/cohort/cohort.py +++ b/lms/lms/doctype/cohort/cohort.py @@ -4,82 +4,76 @@ import frappe from frappe.model.document import Document + class Cohort(Document): - def get_url(self): - return f"{frappe.utils.get_url()}/courses/{self.course}/cohorts/{self.slug}" + def get_url(self): + return f"{frappe.utils.get_url()}/courses/{self.course}/cohorts/{self.slug}" - def get_subgroups(self, include_counts=False, sort_by=None): - names = frappe.get_all("Cohort Subgroup", filters={"cohort": self.name}, pluck="name") - subgroups = [frappe.get_cached_doc("Cohort Subgroup", name) for name in names] - subgroups = sorted(subgroups, key=lambda sg: sg.title) + def get_subgroups(self, include_counts=False, sort_by=None): + names = frappe.get_all("Cohort Subgroup", filters={"cohort": self.name}, pluck="name") + subgroups = [frappe.get_cached_doc("Cohort Subgroup", name) for name in names] + subgroups = sorted(subgroups, key=lambda sg: sg.title) - if include_counts: - mentors = self._get_subgroup_counts("Cohort Mentor") - students = self._get_subgroup_counts("LMS Batch Membership") - join_requests = self._get_subgroup_counts("Cohort Join Request", status="Pending") - for s in subgroups: - s.num_mentors = mentors.get(s.name, 0) - s.num_students = students.get(s.name, 0) - s.num_join_requests = join_requests.get(s.name, 0) + if include_counts: + mentors = self._get_subgroup_counts("Cohort Mentor") + students = self._get_subgroup_counts("LMS Batch Membership") + join_requests = self._get_subgroup_counts("Cohort Join Request", status="Pending") + for s in subgroups: + s.num_mentors = mentors.get(s.name, 0) + s.num_students = students.get(s.name, 0) + s.num_join_requests = join_requests.get(s.name, 0) - if sort_by: - subgroups.sort(key=lambda sg: getattr(sg, sort_by), reverse=True) - return subgroups + if sort_by: + subgroups.sort(key=lambda sg: getattr(sg, sort_by), reverse=True) + return subgroups - def _get_subgroup_counts(self, doctype, **kw): - rows = frappe.get_all(doctype, - filters={"cohort": self.name, **kw}, - fields=['subgroup', 'count(*) as count'], - group_by='subgroup') - return {row['subgroup']: row['count'] for row in rows} + def _get_subgroup_counts(self, doctype, **kw): + rows = frappe.get_all( + doctype, + filters={"cohort": self.name, **kw}, + fields=["subgroup", "count(*) as count"], + group_by="subgroup", + ) + return {row["subgroup"]: row["count"] for row in rows} - def _get_count(self, doctype, **kw): - filters = {"cohort": self.name, **kw} - return frappe.db.count(doctype, filters=filters) + def _get_count(self, doctype, **kw): + filters = {"cohort": self.name, **kw} + return frappe.db.count(doctype, filters=filters) - def get_page_template(self, slug, scope=None): - p = self.get_page(slug, scope=scope) - return p and p.get_template_html() + def get_page_template(self, slug, scope=None): + p = self.get_page(slug, scope=scope) + return p and p.get_template_html() - def get_page(self, slug, scope=None): - for p in self.pages: - if p.slug == slug and scope in [p.scope, None]: - return p + def get_page(self, slug, scope=None): + for p in self.pages: + if p.slug == slug and scope in [p.scope, None]: + return p - def get_pages(self, scope=None): - return [p for p in self.pages if scope in [p.scope, None]] + def get_pages(self, scope=None): + return [p for p in self.pages if scope in [p.scope, None]] - def get_stats(self): - return { - "subgroups": self._get_count("Cohort Subgroup"), - "mentors": self._get_count("Cohort Mentor"), - "students": self._get_count("LMS Batch Membership"), - "join_requests": self._get_count("Cohort Join Request", status="Pending"), - } + def get_stats(self): + return { + "subgroups": self._get_count("Cohort Subgroup"), + "mentors": self._get_count("Cohort Mentor"), + "students": self._get_count("LMS Batch Membership"), + "join_requests": self._get_count("Cohort Join Request", status="Pending"), + } - def get_subgroup(self, slug): - q = dict(cohort=self.name, slug=slug) - name = frappe.db.get_value("Cohort Subgroup", q, "name") - return name and frappe.get_doc("Cohort Subgroup", name) + def get_subgroup(self, slug): + q = dict(cohort=self.name, slug=slug) + name = frappe.db.get_value("Cohort Subgroup", q, "name") + return name and frappe.get_doc("Cohort Subgroup", name) - def get_mentor(self, email): - q = dict(cohort=self.name, email=email) - name = frappe.db.get_value("Cohort Mentor", q, "name") - return name and frappe.get_doc("Cohort Mentor", name) + def get_mentor(self, email): + q = dict(cohort=self.name, email=email) + name = frappe.db.get_value("Cohort Mentor", q, "name") + return name and frappe.get_doc("Cohort Mentor", name) - def is_mentor(self, email): - q = { - "doctype": "Cohort Mentor", - "cohort": self.name, - "email": email - } - return frappe.db.exists(q) + def is_mentor(self, email): + q = {"doctype": "Cohort Mentor", "cohort": self.name, "email": email} + return frappe.db.exists(q) - def is_admin(self, email): - q = { - "doctype": "Cohort Staff", - "cohort": self.name, - "email": email, - "role": "Admin" - } - return frappe.db.exists(q) + def is_admin(self, email): + q = {"doctype": "Cohort Staff", "cohort": self.name, "email": email, "role": "Admin"} + return frappe.db.exists(q) diff --git a/lms/lms/doctype/cohort/test_cohort.py b/lms/lms/doctype/cohort/test_cohort.py index a7cf10ba..72bc1a78 100644 --- a/lms/lms/doctype/cohort/test_cohort.py +++ b/lms/lms/doctype/cohort/test_cohort.py @@ -4,5 +4,6 @@ # import frappe import unittest + class TestCohort(unittest.TestCase): pass diff --git a/lms/lms/doctype/cohort_join_request/cohort_join_request.js b/lms/lms/doctype/cohort_join_request/cohort_join_request.js index 8f6cec18..6654da68 100644 --- a/lms/lms/doctype/cohort_join_request/cohort_join_request.js +++ b/lms/lms/doctype/cohort_join_request/cohort_join_request.js @@ -1,8 +1,7 @@ // Copyright (c) 2021, FOSS United and contributors // For license information, please see license.txt -frappe.ui.form.on('Cohort Join Request', { - // refresh: function(frm) { - - // } +frappe.ui.form.on("Cohort Join Request", { + // refresh: function(frm) { + // } }); diff --git a/lms/lms/doctype/cohort_join_request/cohort_join_request.py b/lms/lms/doctype/cohort_join_request/cohort_join_request.py index 95c975f8..b0f214ac 100644 --- a/lms/lms/doctype/cohort_join_request/cohort_join_request.py +++ b/lms/lms/doctype/cohort_join_request/cohort_join_request.py @@ -4,48 +4,49 @@ import frappe from frappe.model.document import Document + class CohortJoinRequest(Document): - def on_update(self): - if self.status == "Accepted": - self.ensure_student() + def on_update(self): + if self.status == "Accepted": + self.ensure_student() - def ensure_student(self): - # case 1 - user is already a member - q = { - "doctype": "LMS Batch Membership", - "cohort": self.cohort, - "subgroup": self.subgroup, - "member": self.email, - "member_type": "Student" - } - if frappe.db.exists(q): - return + def ensure_student(self): + # case 1 - user is already a member + q = { + "doctype": "LMS Batch Membership", + "cohort": self.cohort, + "subgroup": self.subgroup, + "member": self.email, + "member_type": "Student", + } + if frappe.db.exists(q): + return - # case 2 - user has signed up for this course, possibly not this cohort - cohort = frappe.get_doc("Cohort", self.cohort) + # case 2 - user has signed up for this course, possibly not this cohort + cohort = frappe.get_doc("Cohort", self.cohort) - q = { - "doctype": "LMS Batch Membership", - "course": cohort.course, - "member": self.email, - "member_type": "Student" - } - name = frappe.db.exists(q) - if name: - doc = frappe.get_doc("LMS Batch Membership", name) - doc.cohort = self.cohort - doc.subgroup = self.subgroup - doc.save(ignore_permissions=True) - else: - # case 3 - user has not signed up for this course yet - data = { - "doctype": "LMS Batch Membership", - "course": cohort.course, - "cohort": self.cohort, - "subgroup": self.subgroup, - "member": self.email, - "member_type": "Student", - "role": "Member" - } - doc = frappe.get_doc(data) - doc.insert(ignore_permissions=True) + q = { + "doctype": "LMS Batch Membership", + "course": cohort.course, + "member": self.email, + "member_type": "Student", + } + name = frappe.db.exists(q) + if name: + doc = frappe.get_doc("LMS Batch Membership", name) + doc.cohort = self.cohort + doc.subgroup = self.subgroup + doc.save(ignore_permissions=True) + else: + # case 3 - user has not signed up for this course yet + data = { + "doctype": "LMS Batch Membership", + "course": cohort.course, + "cohort": self.cohort, + "subgroup": self.subgroup, + "member": self.email, + "member_type": "Student", + "role": "Member", + } + doc = frappe.get_doc(data) + doc.insert(ignore_permissions=True) diff --git a/lms/lms/doctype/cohort_join_request/test_cohort_join_request.py b/lms/lms/doctype/cohort_join_request/test_cohort_join_request.py index dd386401..dcc41a01 100644 --- a/lms/lms/doctype/cohort_join_request/test_cohort_join_request.py +++ b/lms/lms/doctype/cohort_join_request/test_cohort_join_request.py @@ -4,5 +4,6 @@ # import frappe import unittest + class TestCohortJoinRequest(unittest.TestCase): pass diff --git a/lms/lms/doctype/cohort_mentor/cohort_mentor.js b/lms/lms/doctype/cohort_mentor/cohort_mentor.js index 81f90950..4194a166 100644 --- a/lms/lms/doctype/cohort_mentor/cohort_mentor.js +++ b/lms/lms/doctype/cohort_mentor/cohort_mentor.js @@ -1,8 +1,7 @@ // Copyright (c) 2021, FOSS United and contributors // For license information, please see license.txt -frappe.ui.form.on('Cohort Mentor', { - // refresh: function(frm) { - - // } +frappe.ui.form.on("Cohort Mentor", { + // refresh: function(frm) { + // } }); diff --git a/lms/lms/doctype/cohort_mentor/cohort_mentor.py b/lms/lms/doctype/cohort_mentor/cohort_mentor.py index 71bf9c4b..e0212e05 100644 --- a/lms/lms/doctype/cohort_mentor/cohort_mentor.py +++ b/lms/lms/doctype/cohort_mentor/cohort_mentor.py @@ -4,9 +4,10 @@ import frappe from frappe.model.document import Document -class CohortMentor(Document): - def get_subgroup(self): - return frappe.get_doc("Cohort Subgroup", self.subgroup) - def get_user(self): - return frappe.get_doc("User", self.email) +class CohortMentor(Document): + def get_subgroup(self): + return frappe.get_doc("Cohort Subgroup", self.subgroup) + + def get_user(self): + return frappe.get_doc("User", self.email) diff --git a/lms/lms/doctype/cohort_mentor/test_cohort_mentor.py b/lms/lms/doctype/cohort_mentor/test_cohort_mentor.py index 06be484e..7ec0cdca 100644 --- a/lms/lms/doctype/cohort_mentor/test_cohort_mentor.py +++ b/lms/lms/doctype/cohort_mentor/test_cohort_mentor.py @@ -4,5 +4,6 @@ # import frappe import unittest + class TestCohortMentor(unittest.TestCase): pass diff --git a/lms/lms/doctype/cohort_staff/cohort_staff.js b/lms/lms/doctype/cohort_staff/cohort_staff.js index 8deaab23..415b9ff2 100644 --- a/lms/lms/doctype/cohort_staff/cohort_staff.js +++ b/lms/lms/doctype/cohort_staff/cohort_staff.js @@ -1,8 +1,7 @@ // Copyright (c) 2021, FOSS United and contributors // For license information, please see license.txt -frappe.ui.form.on('Cohort Staff', { - // refresh: function(frm) { - - // } +frappe.ui.form.on("Cohort Staff", { + // refresh: function(frm) { + // } }); diff --git a/lms/lms/doctype/cohort_staff/cohort_staff.py b/lms/lms/doctype/cohort_staff/cohort_staff.py index 6febbcc4..f3055bc0 100644 --- a/lms/lms/doctype/cohort_staff/cohort_staff.py +++ b/lms/lms/doctype/cohort_staff/cohort_staff.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class CohortStaff(Document): pass diff --git a/lms/lms/doctype/cohort_staff/test_cohort_staff.py b/lms/lms/doctype/cohort_staff/test_cohort_staff.py index c27f423e..25b2fa72 100644 --- a/lms/lms/doctype/cohort_staff/test_cohort_staff.py +++ b/lms/lms/doctype/cohort_staff/test_cohort_staff.py @@ -4,5 +4,6 @@ # import frappe import unittest + class TestCohortStaff(unittest.TestCase): pass diff --git a/lms/lms/doctype/cohort_subgroup/cohort_subgroup.js b/lms/lms/doctype/cohort_subgroup/cohort_subgroup.js index 9c851cf0..966cfbe0 100644 --- a/lms/lms/doctype/cohort_subgroup/cohort_subgroup.js +++ b/lms/lms/doctype/cohort_subgroup/cohort_subgroup.js @@ -1,8 +1,7 @@ // Copyright (c) 2021, FOSS United and contributors // For license information, please see license.txt -frappe.ui.form.on('Cohort Subgroup', { - // refresh: function(frm) { - - // } +frappe.ui.form.on("Cohort Subgroup", { + // refresh: function(frm) { + // } }); diff --git a/lms/lms/doctype/cohort_subgroup/cohort_subgroup.py b/lms/lms/doctype/cohort_subgroup/cohort_subgroup.py index dfb810b0..ebc8a452 100644 --- a/lms/lms/doctype/cohort_subgroup/cohort_subgroup.py +++ b/lms/lms/doctype/cohort_subgroup/cohort_subgroup.py @@ -5,91 +5,84 @@ import frappe from frappe.model.document import Document from frappe.utils import random_string + class CohortSubgroup(Document): - def before_save(self): - if not self.invite_code: - self.invite_code = random_string(8) + def before_save(self): + if not self.invite_code: + self.invite_code = random_string(8) - def get_url(self): - cohort = frappe.get_doc("Cohort", self.cohort) - return f"{frappe.utils.get_url()}/courses/{self.course}/subgroups/{cohort.slug}/{self.slug}" + def get_url(self): + cohort = frappe.get_doc("Cohort", self.cohort) + return ( + f"{frappe.utils.get_url()}/courses/{self.course}/subgroups/{cohort.slug}/{self.slug}" + ) - def get_invite_link(self): - cohort = frappe.get_doc("Cohort", self.cohort) - return f"{frappe.utils.get_url()}/courses/{self.course}/join/{cohort.slug}/{self.slug}/{self.invite_code}" + def get_invite_link(self): + cohort = frappe.get_doc("Cohort", self.cohort) + return f"{frappe.utils.get_url()}/courses/{self.course}/join/{cohort.slug}/{self.slug}/{self.invite_code}" - def has_student(self, email): - """Check if given user is a student of this subgroup. - """ - q = { - "doctype": "LMS Batch Membership", - "subgroup": self.name, - "member": email - } - return frappe.db.exists(q) + def has_student(self, email): + """Check if given user is a student of this subgroup.""" + q = {"doctype": "LMS Batch Membership", "subgroup": self.name, "member": email} + return frappe.db.exists(q) - def has_join_request(self, email): - """Check if given user is a student of this subgroup. - """ - q = { - "doctype": "Cohort Join Request", - "subgroup": self.name, - "email": email - } - return frappe.db.exists(q) + def has_join_request(self, email): + """Check if given user is a student of this subgroup.""" + q = {"doctype": "Cohort Join Request", "subgroup": self.name, "email": email} + return frappe.db.exists(q) - def get_join_requests(self, status="Pending"): - q = { - "subgroup": self.name, - "status": status - } - return frappe.get_all("Cohort Join Request", filters=q, fields=["*"], order_by="creation desc") + def get_join_requests(self, status="Pending"): + q = {"subgroup": self.name, "status": status} + return frappe.get_all( + "Cohort Join Request", filters=q, fields=["*"], order_by="creation desc" + ) - def get_mentors(self): - emails = frappe.get_all("Cohort Mentor", filters={"subgroup": self.name}, fields=["email"], pluck='email') - return self._get_users(emails) + def get_mentors(self): + emails = frappe.get_all( + "Cohort Mentor", filters={"subgroup": self.name}, fields=["email"], pluck="email" + ) + return self._get_users(emails) - def get_students(self): - emails = frappe.get_all("LMS Batch Membership", - filters={"subgroup": self.name}, - fields=["member"], - pluck='member', - page_length=1000) - return self._get_users(emails) + def get_students(self): + emails = frappe.get_all( + "LMS Batch Membership", + filters={"subgroup": self.name}, + fields=["member"], + pluck="member", + page_length=1000, + ) + return self._get_users(emails) - def _get_users(self, emails): - users = [frappe.get_cached_doc("User", email) for email in emails] - return sorted(users, key=lambda user: user.full_name) + def _get_users(self, emails): + users = [frappe.get_cached_doc("User", email) for email in emails] + return sorted(users, key=lambda user: user.full_name) - def is_mentor(self, email): - q = { - "doctype": "Cohort Mentor", - "subgroup": self.name, - "email": email - } - return frappe.db.exists(q) + def is_mentor(self, email): + q = {"doctype": "Cohort Mentor", "subgroup": self.name, "email": email} + return frappe.db.exists(q) - def is_manager(self, email): - """Returns True if the given user is a manager of this subgroup. + def is_manager(self, email): + """Returns True if the given user is a manager of this subgroup. - Mentors of the subgroup, admins of the Cohort are considered as managers. - """ - return self.is_mentor(email) or self.get_cohort().is_admin(email) + Mentors of the subgroup, admins of the Cohort are considered as managers. + """ + return self.is_mentor(email) or self.get_cohort().is_admin(email) - def get_cohort(self): - return frappe.get_doc("Cohort", self.cohort) + def get_cohort(self): + return frappe.get_doc("Cohort", self.cohort) - def add_mentor(self, email): - d = { - "doctype": "Cohort Mentor", - "subgroup": self.name, - "cohort": self.cohort, - "email": email - } - if frappe.db.exists(d): - return - doc = frappe.get_doc(d) - doc.insert(ignore_permissions=True) + def add_mentor(self, email): + d = { + "doctype": "Cohort Mentor", + "subgroup": self.name, + "cohort": self.cohort, + "email": email, + } + if frappe.db.exists(d): + return + doc = frappe.get_doc(d) + doc.insert(ignore_permissions=True) -#def after_doctype_insert(): + +# def after_doctype_insert(): # frappe.db.add_unique("Cohort Subgroup", ("cohort", "slug")) diff --git a/lms/lms/doctype/cohort_subgroup/test_cohort_subgroup.py b/lms/lms/doctype/cohort_subgroup/test_cohort_subgroup.py index 8715a234..a46aa304 100644 --- a/lms/lms/doctype/cohort_subgroup/test_cohort_subgroup.py +++ b/lms/lms/doctype/cohort_subgroup/test_cohort_subgroup.py @@ -4,5 +4,6 @@ # import frappe import unittest + class TestCohortSubgroup(unittest.TestCase): pass diff --git a/lms/lms/doctype/cohort_web_page/cohort_web_page.py b/lms/lms/doctype/cohort_web_page/cohort_web_page.py index b290954e..53bcbc6e 100644 --- a/lms/lms/doctype/cohort_web_page/cohort_web_page.py +++ b/lms/lms/doctype/cohort_web_page/cohort_web_page.py @@ -4,6 +4,7 @@ import frappe from frappe.model.document import Document + class CohortWebPage(Document): - def get_template_html(self): - return frappe.get_doc("Web Template", self.template).template + def get_template_html(self): + return frappe.get_doc("Web Template", self.template).template diff --git a/lms/lms/doctype/course_chapter/course_chapter.js b/lms/lms/doctype/course_chapter/course_chapter.js index 9e24b54d..a1951164 100644 --- a/lms/lms/doctype/course_chapter/course_chapter.js +++ b/lms/lms/doctype/course_chapter/course_chapter.js @@ -1,14 +1,14 @@ // Copyright (c) 2021, FOSS United and contributors // For license information, please see license.txt -frappe.ui.form.on('Course Chapter', { - onload: function (frm) { - frm.set_query("lesson", "lessons", function () { - return { - filters: { - "chapter": frm.doc.name, - } - }; - }); - } +frappe.ui.form.on("Course Chapter", { + onload: function (frm) { + frm.set_query("lesson", "lessons", function () { + return { + filters: { + chapter: frm.doc.name, + }, + }; + }); + }, }); diff --git a/lms/lms/doctype/course_chapter/course_chapter.py b/lms/lms/doctype/course_chapter/course_chapter.py index 9556f2ae..63febc33 100644 --- a/lms/lms/doctype/course_chapter/course_chapter.py +++ b/lms/lms/doctype/course_chapter/course_chapter.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class CourseChapter(Document): pass diff --git a/lms/lms/doctype/course_chapter/test_course_chapter.py b/lms/lms/doctype/course_chapter/test_course_chapter.py index d14cd9fd..ebeda074 100644 --- a/lms/lms/doctype/course_chapter/test_course_chapter.py +++ b/lms/lms/doctype/course_chapter/test_course_chapter.py @@ -4,5 +4,6 @@ # import frappe import unittest + class TestCourseChapter(unittest.TestCase): pass diff --git a/lms/lms/doctype/course_evaluator/course_evaluator.js b/lms/lms/doctype/course_evaluator/course_evaluator.js index dd14d6e0..ef834b5c 100644 --- a/lms/lms/doctype/course_evaluator/course_evaluator.js +++ b/lms/lms/doctype/course_evaluator/course_evaluator.js @@ -1,14 +1,14 @@ // Copyright (c) 2022, Frappe and contributors // For license information, please see license.txt -frappe.ui.form.on('Course Evaluator', { +frappe.ui.form.on("Course Evaluator", { onload: (frm) => { - frm.set_query('evaluator', function(doc) { + frm.set_query("evaluator", function (doc) { return { filters: { - "ignore_user_type": 1, - } + ignore_user_type: 1, + }, }; }); - } + }, }); diff --git a/lms/lms/doctype/course_evaluator/course_evaluator.py b/lms/lms/doctype/course_evaluator/course_evaluator.py index cf070a21..c868c2f8 100644 --- a/lms/lms/doctype/course_evaluator/course_evaluator.py +++ b/lms/lms/doctype/course_evaluator/course_evaluator.py @@ -5,46 +5,53 @@ import frappe from frappe import _ from frappe.model.document import Document + class CourseEvaluator(Document): + def validate(self): + self.validate_time_slots() - def validate(self): - self.validate_time_slots() + def validate_time_slots(self): + 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): - 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) - 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): - same_day_slots = list(filter(lambda x: x.day == schedule.day and x.name != schedule.name , self.schedule)) - overlap = False + for slot in same_day_slots: + if schedule.start_time <= slot.start_time < schedule.end_time: + 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 schedule.start_time <= slot.start_time < schedule.end_time: - 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.")) - if overlap: - frappe.throw(_("Slot Times are overlapping for some schedules.")) @frappe.whitelist() def get_schedule(course, date): - evaluator = frappe.db.get_value("LMS Course", course, "evaluator") - all_slots = frappe.get_all("Evaluator Schedule", - filters = { "parent": evaluator }, - fields = ["day", "start_time", "end_time"]) - booked_slots = frappe.get_all("LMS Certificate Request", - filters = {"evaluator": evaluator, "date": date}, - fields = ["start_time"]) + evaluator = frappe.db.get_value("LMS Course", course, "evaluator") + all_slots = frappe.get_all( + "Evaluator Schedule", + filters={"parent": evaluator}, + fields=["day", "start_time", "end_time"], + ) + booked_slots = frappe.get_all( + "LMS Certificate Request", + filters={"evaluator": evaluator, "date": date}, + fields=["start_time"], + ) - for slot in booked_slots: - same_slot = list(filter(lambda x: x.start_time == slot.start_time, all_slots)) - if len(same_slot): - all_slots.remove(same_slot[0]) + for slot in booked_slots: + same_slot = list(filter(lambda x: x.start_time == slot.start_time, all_slots)) + if len(same_slot): + all_slots.remove(same_slot[0]) - return all_slots + return all_slots diff --git a/lms/lms/doctype/course_instructor/course_instructor.py b/lms/lms/doctype/course_instructor/course_instructor.py index d229178c..68acf016 100644 --- a/lms/lms/doctype/course_instructor/course_instructor.py +++ b/lms/lms/doctype/course_instructor/course_instructor.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class CourseInstructor(Document): pass diff --git a/lms/lms/doctype/course_lesson/course_lesson.js b/lms/lms/doctype/course_lesson/course_lesson.js index f7722723..c6384125 100644 --- a/lms/lms/doctype/course_lesson/course_lesson.js +++ b/lms/lms/doctype/course_lesson/course_lesson.js @@ -1,17 +1,21 @@ // Copyright (c) 2021, FOSS United and contributors // For license information, please see license.txt -frappe.ui.form.on('Course Lesson', { +frappe.ui.form.on("Course Lesson", { setup: function (frm) { - frm.trigger('setup_help'); + frm.trigger("setup_help"); }, setup_help(frm) { let quiz_link = ` ${__("Quiz List")} `; - let exercise_link = ` ${__("Exercise List")} `; + let exercise_link = ` ${__( + "Exercise List" + )} `; let file_link = ` ${__("File DocType")} `; - frm.get_field('help').html(` -

${__("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.")}

+ frm.get_field("help").html(` +

${__( + "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." + )}

@@ -56,7 +66,10 @@ frappe.ui.form.on('Course Lesson', { {{ Quiz("lms_quiz_id") }} @@ -67,7 +80,10 @@ frappe.ui.form.on('Course Lesson', { {{ Video("url_of_source") }} @@ -78,7 +94,10 @@ frappe.ui.form.on('Course Lesson', { {{ Exercise("exercise_id") }} @@ -125,5 +144,5 @@ frappe.ui.form.on('Course Lesson', {
@@ -33,17 +37,23 @@ frappe.ui.form.on('Course Lesson', { - ${ __("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." + )}
  • - ${ __("Upload the video on youtube.") } + ${__("Upload the video on youtube.")}
  • - ${ __("When you share a youtube video, it shows an option called Embed.") } + ${__( + "When you share a youtube video, it shows an option called Embed." + )}
  • - ${ __("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." + )}
- ${ __("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] + )}
- ${ __("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] + )}
- ${ __("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] + )}
`); - } + }, }); diff --git a/lms/lms/doctype/course_lesson/course_lesson.py b/lms/lms/doctype/course_lesson/course_lesson.py index 66bdde72..3a29ee4d 100644 --- a/lms/lms/doctype/course_lesson/course_lesson.py +++ b/lms/lms/doctype/course_lesson/course_lesson.py @@ -1,125 +1,119 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2021, FOSS United and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe import _ from frappe.model.document import Document -from ...md import find_macros + from lms.lms.utils import get_course_progress, get_lesson_url +from ...md import find_macros + class CourseLesson(Document): - def validate(self): - #self.check_and_create_folder() - self.validate_quiz_id() + def validate(self): + # self.check_and_create_folder() + 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): - if self.quiz_id and not frappe.db.exists("LMS Quiz", self.quiz_id): - frappe.throw(_("Invalid Quiz ID")) + def on_update(self): + dynamic_documents = ["Exercise", "Quiz"] + for section in dynamic_documents: + self.update_lesson_name_in_document(section) + def update_lesson_name_in_document(self, section): + doctype_map = {"Exercise": "Exercise", "Quiz": "LMS Quiz"} + macros = find_macros(self.body) + documents = [value for name, value in macros if name == section] + index = 1 + for name in documents: + e = frappe.get_doc(doctype_map[section], name) + e.lesson = self.name + e.index_ = index + e.save(ignore_permissions=True) + index += 1 + self.update_orphan_documents(doctype_map[section], documents) - def on_update(self): - dynamic_documents = ["Exercise", "Quiz"] - for section in dynamic_documents: - self.update_lesson_name_in_document(section) + def update_orphan_documents(self, doctype, documents): + """Updates the documents that were previously part of this lesson, + but not any more. + """ + linked_documents = { + row["name"] for row in frappe.get_all(doctype, {"lesson": self.name}) + } + active_documents = set(documents) + orphan_documents = linked_documents - active_documents + for name in orphan_documents: + ex = frappe.get_doc(doctype, name) + ex.lesson = None + ex.index_ = 0 + ex.index_label = "" + ex.save() + def check_and_create_folder(self): + 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): - 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 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 update_orphan_documents(self, doctype, documents): - """Updates the documents that were previously part of this lesson, - but not any more. - """ - linked_documents = {row['name'] for row in frappe.get_all(doctype, {"lesson": self.name})} - active_documents = set(documents) - orphan_documents = linked_documents - active_documents - for name in orphan_documents: - ex = frappe.get_doc(doctype, name) - ex.lesson = None - ex.index_ = 0 - ex.index_label = "" - ex.save() + def get_progress(self): + return frappe.db.get_value( + "LMS Course Progress", {"lesson": self.name, "owner": frappe.session.user}, "status" + ) - - 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 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 + def get_slugified_class(self): + if self.get_progress(): + return ("").join([s for s in self.get_progress().lower().split()]) + return @frappe.whitelist() def save_progress(lesson, course, status): - membership = frappe.db.exists("LMS Batch Membership", { - "member": frappe.session.user, - "course": course - }) - if not membership: - return + membership = frappe.db.exists( + "LMS Batch Membership", {"member": frappe.session.user, "course": course} + ) + if not membership: + return - if frappe.db.exists("LMS Course Progress", { - "lesson": lesson, - "owner": frappe.session.user, - "course": course - }): - doc = frappe.get_doc("LMS Course Progress", { - "lesson": lesson, - "owner": frappe.session.user, - "course": course - }) - doc.status = status - doc.save(ignore_permissions=True) - else: - frappe.get_doc({ - "doctype": "LMS Course Progress", - "lesson": lesson, - "status": status, - }).save(ignore_permissions=True) + if frappe.db.exists( + "LMS Course Progress", + {"lesson": lesson, "owner": frappe.session.user, "course": course}, + ): + doc = frappe.get_doc( + "LMS Course Progress", + {"lesson": lesson, "owner": frappe.session.user, "course": course}, + ) + doc.status = status + doc.save(ignore_permissions=True) + else: + frappe.get_doc( + { + "doctype": "LMS Course Progress", + "lesson": lesson, + "status": status, + } + ).save(ignore_permissions=True) - progress = get_course_progress(course) - frappe.db.set_value("LMS Batch Membership", membership, "progress", progress) - return progress + progress = get_course_progress(course) + frappe.db.set_value("LMS Batch Membership", membership, "progress", progress) + return progress @frappe.whitelist() def get_lesson_info(chapter): - return frappe.db.get_value("Course Chapter", chapter, "course") + return frappe.db.get_value("Course Chapter", chapter, "course") diff --git a/lms/lms/doctype/course_lesson/test_course_lesson.py b/lms/lms/doctype/course_lesson/test_course_lesson.py index c5c40a94..46eada82 100644 --- a/lms/lms/doctype/course_lesson/test_course_lesson.py +++ b/lms/lms/doctype/course_lesson/test_course_lesson.py @@ -4,5 +4,6 @@ # import frappe import unittest + class TestCourseLesson(unittest.TestCase): pass diff --git a/lms/lms/doctype/education_detail/education_detail.py b/lms/lms/doctype/education_detail/education_detail.py index 0e28779c..ab859e54 100644 --- a/lms/lms/doctype/education_detail/education_detail.py +++ b/lms/lms/doctype/education_detail/education_detail.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class EducationDetail(Document): pass diff --git a/lms/lms/doctype/evaluator_schedule/evaluator_schedule.py b/lms/lms/doctype/evaluator_schedule/evaluator_schedule.py index 894126db..48526caa 100644 --- a/lms/lms/doctype/evaluator_schedule/evaluator_schedule.py +++ b/lms/lms/doctype/evaluator_schedule/evaluator_schedule.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class EvaluatorSchedule(Document): pass diff --git a/lms/lms/doctype/exercise/exercise.js b/lms/lms/doctype/exercise/exercise.js index 740634b4..ca8b3ec4 100644 --- a/lms/lms/doctype/exercise/exercise.js +++ b/lms/lms/doctype/exercise/exercise.js @@ -1,8 +1,7 @@ // Copyright (c) 2021, FOSS United and contributors // For license information, please see license.txt -frappe.ui.form.on('Exercise', { - // refresh: function(frm) { - - // } +frappe.ui.form.on("Exercise", { + // refresh: function(frm) { + // } }); diff --git a/lms/lms/doctype/exercise/exercise.py b/lms/lms/doctype/exercise/exercise.py index cf4b2667..2ba58e3a 100644 --- a/lms/lms/doctype/exercise/exercise.py +++ b/lms/lms/doctype/exercise/exercise.py @@ -3,51 +3,50 @@ import frappe from frappe.model.document import Document + from lms.lms.utils import get_membership + class Exercise(Document): - def get_user_submission(self): - """Returns the latest submission for this user. - """ - user = frappe.session.user - if not user or user == "Guest": - return + def get_user_submission(self): + """Returns the latest submission for this user.""" + user = frappe.session.user + if not user or user == "Guest": + return - result = frappe.get_all('Exercise Submission', - fields="*", - filters={ - "owner": user, - "exercise": self.name - }, - order_by="creation desc", - page_length=1) + result = frappe.get_all( + "Exercise Submission", + fields="*", + filters={"owner": user, "exercise": self.name}, + order_by="creation desc", + page_length=1, + ) - if result: - return result[0] + if result: + return result[0] - def submit(self, code): - """Submits the given code as solution to exercise. - """ - user = frappe.session.user - if not user or user == "Guest": - return + def submit(self, code): + """Submits the given code as solution to exercise.""" + user = frappe.session.user + if not user or user == "Guest": + return - old_submission = self.get_user_submission() - if old_submission and old_submission.solution == code: - return old_submission + old_submission = self.get_user_submission() + if old_submission and old_submission.solution == code: + return old_submission - member = get_membership(self.course, frappe.session.user) + member = get_membership(self.course, frappe.session.user) - doc = frappe.get_doc( - doctype="Exercise Submission", - exercise=self.name, - exercise_title=self.title, - course=self.course, - lesson=self.lesson, - batch=member.batch, - solution=code, - member=member.name) - doc.insert(ignore_permissions=True) - - return doc + doc = frappe.get_doc( + doctype="Exercise Submission", + exercise=self.name, + exercise_title=self.title, + course=self.course, + lesson=self.lesson, + batch=member.batch, + solution=code, + member=member.name, + ) + doc.insert(ignore_permissions=True) + return doc diff --git a/lms/lms/doctype/exercise/test_exercise.py b/lms/lms/doctype/exercise/test_exercise.py index 9deaa690..a57b2c4e 100644 --- a/lms/lms/doctype/exercise/test_exercise.py +++ b/lms/lms/doctype/exercise/test_exercise.py @@ -1,50 +1,54 @@ # Copyright (c) 2021, FOSS United and Contributors # See license.txt -import frappe import unittest + +import frappe + from lms.lms.doctype.lms_course.test_lms_course import new_course + 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): - 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 test_exercise(self): + e = self.new_exercise() + assert e.get_user_submission() is None - def test_exercise(self): - e = self.new_exercise() - assert e.get_user_submission() is None + def test_exercise_submission(self): + e = self.new_exercise() + submission = e.submit("circle(100, 100, 50)") + assert submission is not None + assert submission.exercise == e.name + assert submission.course == e.course - def test_exercise_submission(self): - e = self.new_exercise() - submission = e.submit("circle(100, 100, 50)") - assert submission is not None - assert submission.exercise == e.name - assert submission.course == e.course + user_submission = e.get_user_submission() + assert user_submission is not None + assert user_submission.name == submission.name - user_submission = e.get_user_submission() - assert user_submission is not None - assert user_submission.name == submission.name - - def tearDown(self): - frappe.db.sql('delete from `tabLMS Batch Membership`') - 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`") diff --git a/lms/lms/doctype/exercise_latest_submission/exercise_latest_submission.js b/lms/lms/doctype/exercise_latest_submission/exercise_latest_submission.js index aea1f03d..236b4459 100644 --- a/lms/lms/doctype/exercise_latest_submission/exercise_latest_submission.js +++ b/lms/lms/doctype/exercise_latest_submission/exercise_latest_submission.js @@ -1,8 +1,7 @@ // Copyright (c) 2021, Frappe and contributors // For license information, please see license.txt -frappe.ui.form.on('Exercise Latest Submission', { - // refresh: function(frm) { - - // } +frappe.ui.form.on("Exercise Latest Submission", { + // refresh: function(frm) { + // } }); diff --git a/lms/lms/doctype/exercise_latest_submission/exercise_latest_submission.py b/lms/lms/doctype/exercise_latest_submission/exercise_latest_submission.py index 43a7f5cd..11a1cf39 100644 --- a/lms/lms/doctype/exercise_latest_submission/exercise_latest_submission.py +++ b/lms/lms/doctype/exercise_latest_submission/exercise_latest_submission.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class ExerciseLatestSubmission(Document): pass diff --git a/lms/lms/doctype/exercise_latest_submission/test_exercise_latest_submission.py b/lms/lms/doctype/exercise_latest_submission/test_exercise_latest_submission.py index 76825ecd..7f8809c6 100644 --- a/lms/lms/doctype/exercise_latest_submission/test_exercise_latest_submission.py +++ b/lms/lms/doctype/exercise_latest_submission/test_exercise_latest_submission.py @@ -4,5 +4,6 @@ # import frappe import unittest + class TestExerciseLatestSubmission(unittest.TestCase): pass diff --git a/lms/lms/doctype/exercise_submission/exercise_submission.js b/lms/lms/doctype/exercise_submission/exercise_submission.js index 5f1d8399..2bf7b6e3 100644 --- a/lms/lms/doctype/exercise_submission/exercise_submission.js +++ b/lms/lms/doctype/exercise_submission/exercise_submission.js @@ -1,8 +1,7 @@ // Copyright (c) 2021, FOSS United and contributors // For license information, please see license.txt -frappe.ui.form.on('Exercise Submission', { - // refresh: function(frm) { - - // } +frappe.ui.form.on("Exercise Submission", { + // refresh: function(frm) { + // } }); diff --git a/lms/lms/doctype/exercise_submission/exercise_submission.py b/lms/lms/doctype/exercise_submission/exercise_submission.py index 7209826f..cf2c80cc 100644 --- a/lms/lms/doctype/exercise_submission/exercise_submission.py +++ b/lms/lms/doctype/exercise_submission/exercise_submission.py @@ -4,21 +4,26 @@ import frappe from frappe.model.document import Document -class ExerciseSubmission(Document): - def on_update(self): - self.update_latest_submission() - def update_latest_submission(self): - names = frappe.get_all("Exercise Latest Submission", {"exercise": self.exercise, "member": self.member}) - if names: - doc = frappe.get_doc("Exercise Latest Submission", names[0]) - doc.latest_submission = self.name - doc.save(ignore_permissions=True) - else: - doc = frappe.get_doc({ - "doctype": "Exercise Latest Submission", - "exercise": self.exercise, - "member": self.member, - "latest_submission": self.name - }) - doc.insert(ignore_permissions=True) +class ExerciseSubmission(Document): + def on_update(self): + self.update_latest_submission() + + def update_latest_submission(self): + names = frappe.get_all( + "Exercise Latest Submission", {"exercise": self.exercise, "member": self.member} + ) + if names: + doc = frappe.get_doc("Exercise Latest Submission", names[0]) + doc.latest_submission = self.name + doc.save(ignore_permissions=True) + else: + doc = frappe.get_doc( + { + "doctype": "Exercise Latest Submission", + "exercise": self.exercise, + "member": self.member, + "latest_submission": self.name, + } + ) + doc.insert(ignore_permissions=True) diff --git a/lms/lms/doctype/exercise_submission/test_exercise_submission.py b/lms/lms/doctype/exercise_submission/test_exercise_submission.py index eed851a5..71704011 100644 --- a/lms/lms/doctype/exercise_submission/test_exercise_submission.py +++ b/lms/lms/doctype/exercise_submission/test_exercise_submission.py @@ -4,5 +4,6 @@ # import frappe import unittest + class TestExerciseSubmission(unittest.TestCase): pass diff --git a/lms/lms/doctype/function/function.js b/lms/lms/doctype/function/function.js index 76de5320..2bf1b7c8 100644 --- a/lms/lms/doctype/function/function.js +++ b/lms/lms/doctype/function/function.js @@ -1,8 +1,7 @@ // Copyright (c) 2021, Frappe and contributors // For license information, please see license.txt -frappe.ui.form.on('Function', { - // refresh: function(frm) { - - // } +frappe.ui.form.on("Function", { + // refresh: function(frm) { + // } }); diff --git a/lms/lms/doctype/function/function.py b/lms/lms/doctype/function/function.py index 39942f7a..b8d3dc06 100644 --- a/lms/lms/doctype/function/function.py +++ b/lms/lms/doctype/function/function.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class Function(Document): pass diff --git a/lms/lms/doctype/function/test_function.py b/lms/lms/doctype/function/test_function.py index bf87dc73..170c62e7 100644 --- a/lms/lms/doctype/function/test_function.py +++ b/lms/lms/doctype/function/test_function.py @@ -4,5 +4,6 @@ # import frappe import unittest + class TestFunction(unittest.TestCase): pass diff --git a/lms/lms/doctype/industry/industry.js b/lms/lms/doctype/industry/industry.js index 93534dbf..39394fa9 100644 --- a/lms/lms/doctype/industry/industry.js +++ b/lms/lms/doctype/industry/industry.js @@ -1,8 +1,7 @@ // Copyright (c) 2021, Frappe and contributors // For license information, please see license.txt -frappe.ui.form.on('Industry', { - // refresh: function(frm) { - - // } +frappe.ui.form.on("Industry", { + // refresh: function(frm) { + // } }); diff --git a/lms/lms/doctype/industry/industry.py b/lms/lms/doctype/industry/industry.py index 098084ca..46099ec3 100644 --- a/lms/lms/doctype/industry/industry.py +++ b/lms/lms/doctype/industry/industry.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class Industry(Document): pass diff --git a/lms/lms/doctype/industry/test_industry.py b/lms/lms/doctype/industry/test_industry.py index d8c7f632..ceee2d7f 100644 --- a/lms/lms/doctype/industry/test_industry.py +++ b/lms/lms/doctype/industry/test_industry.py @@ -4,5 +4,6 @@ # import frappe import unittest + class TestIndustry(unittest.TestCase): pass diff --git a/lms/lms/doctype/invite_request/invite_request.js b/lms/lms/doctype/invite_request/invite_request.js index 1d4e0738..ffdd101f 100644 --- a/lms/lms/doctype/invite_request/invite_request.js +++ b/lms/lms/doctype/invite_request/invite_request.js @@ -1,8 +1,7 @@ // Copyright (c) 2021, FOSS United and contributors // For license information, please see license.txt -frappe.ui.form.on('Invite Request', { - // refresh: function(frm) { - - // } +frappe.ui.form.on("Invite Request", { + // refresh: function(frm) { + // } }); diff --git a/lms/lms/doctype/invite_request/invite_request.py b/lms/lms/doctype/invite_request/invite_request.py index 84ac4a61..7848529c 100644 --- a/lms/lms/doctype/invite_request/invite_request.py +++ b/lms/lms/doctype/invite_request/invite_request.py @@ -1,90 +1,92 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2021, FOSS United and contributors # For license information, please see license.txt -from __future__ import unicode_literals +import json + import frappe from frappe import _ from frappe.model.document import Document -import json from frappe.utils.password import get_decrypted_password + class InviteRequest(Document): - def on_update(self): - if self.has_value_changed("status") and self.status == "Approved": - self.send_email() + def on_update(self): + if self.has_value_changed("status") and self.status == "Approved": + self.send_email() - def create_user(self, password): - full_name_split = self.full_name.split(" ") - user = frappe.get_doc({ - "doctype": "User", - "email": self.signup_email, - "first_name": full_name_split[0], - "last_name": full_name_split[1] if len(full_name_split) > 1 else "", - "username": self.username, - "send_welcome_email": 0, - "user_type": "Website User", - "new_password": password - }) - user.save(ignore_permissions=True) - return user + def create_user(self, password): + full_name_split = self.full_name.split(" ") + user = frappe.get_doc( + { + "doctype": "User", + "email": self.signup_email, + "first_name": full_name_split[0], + "last_name": full_name_split[1] if len(full_name_split) > 1 else "", + "username": self.username, + "send_welcome_email": 0, + "user_type": "Website User", + "new_password": password, + } + ) + user.save(ignore_permissions=True) + return user - def send_email(self): - site_name = "Mon.School" - subject = _("Welcome to {0}!").format(site_name) + def send_email(self): + site_name = "Mon.School" + 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) def create_invite_request(invite_email): - if not frappe.utils.validate_email_address(invite_email): - return "invalid email" + if not frappe.utils.validate_email_address(invite_email): + return "invalid email" - if frappe.db.exists("User", invite_email): - return "user" + if frappe.db.exists("User", invite_email): + return "user" - if frappe.db.exists("Invite Request", {"invite_email": invite_email}): - return "invite" + if frappe.db.exists("Invite Request", {"invite_email": invite_email}): + return "invite" - frappe.get_doc({ - "doctype": "Invite Request", - "invite_email": invite_email, - "status": "Approved" - }).save(ignore_permissions=True) - return "OK" + frappe.get_doc( + {"doctype": "Invite Request", "invite_email": invite_email, "status": "Approved"} + ).save(ignore_permissions=True) + return "OK" @frappe.whitelist(allow_guest=True) def update_invite(data): - data = frappe._dict(json.loads(data)) if type(data) == str else frappe._dict(data) + data = frappe._dict(json.loads(data)) if type(data) == str else frappe._dict(data) - try: - doc = frappe.get_doc("Invite Request", data.invite_code) - except frappe.DoesNotExistError: - frappe.throw(_("Invalid Invite Code.")) + try: + doc = frappe.get_doc("Invite Request", data.invite_code) + except frappe.DoesNotExistError: + frappe.throw(_("Invalid Invite Code.")) - doc.signup_email = data.signup_email - doc.username = data.username - doc.full_name = data.full_name - doc.invite_code = data.invite_code - doc.save(ignore_permissions=True) + doc.signup_email = data.signup_email + doc.username = data.username + doc.full_name = data.full_name + doc.invite_code = data.invite_code + doc.save(ignore_permissions=True) - user = doc.create_user(data.password) - if user: - doc.status = "Registered" - doc.save(ignore_permissions=True) + user = doc.create_user(data.password) + if user: + doc.status = "Registered" + doc.save(ignore_permissions=True) - return "OK" + return "OK" diff --git a/lms/lms/doctype/invite_request/test_invite_request.py b/lms/lms/doctype/invite_request/test_invite_request.py index 491bac17..e8122f80 100644 --- a/lms/lms/doctype/invite_request/test_invite_request.py +++ b/lms/lms/doctype/invite_request/test_invite_request.py @@ -1,62 +1,82 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2021, FOSS United and Contributors # See license.txt -from __future__ import unicode_literals -from lms.lms.doctype.invite_request.invite_request import create_invite_request, update_invite -import frappe import unittest +import frappe + +from lms.lms.doctype.invite_request.invite_request import ( + create_invite_request, update_invite) + + class TestInviteRequest(unittest.TestCase): + @classmethod + def setUpClass(self): + create_invite_request("test_invite@example.com") - @classmethod - def setUpClass(self): - create_invite_request("test_invite@example.com") + def test_create_invite_request(self): + if frappe.db.exists("Invite Request", {"invite_email": "test_invite@example.com"}): + invite = frappe.db.get_value( + "Invite Request", + filters={"invite_email": "test_invite@example.com"}, + fieldname=["invite_email", "status", "signup_email"], + as_dict=True, + ) + self.assertEqual(invite.status, "Approved") + self.assertEqual(invite.signup_email, None) - def test_create_invite_request(self): - if frappe.db.exists("Invite Request", {"invite_email": "test_invite@example.com"}): - invite = frappe.db.get_value("Invite Request", - filters={"invite_email": "test_invite@example.com"}, - fieldname=["invite_email", "status", "signup_email"], - as_dict=True) - self.assertEqual(invite.status, "Approved") - self.assertEqual(invite.signup_email, None) + def test_create_invite_request_update(self): + if frappe.db.exists("Invite Request", {"invite_email": "test_invite@example.com"}): - def test_create_invite_request_update(self): - if frappe.db.exists("Invite Request", {"invite_email": "test_invite@example.com"}): + data = { + "signup_email": "test_invite@example.com", + "username": "test_invite", + "full_name": "Test Invite", + "password": "Test@invite", + "invite_code": frappe.db.get_value( + "Invite Request", {"invite_email": "test_invite@example.com"}, "name" + ), + } - data = { - "signup_email": "test_invite@example.com", - "username": "test_invite", - "full_name": "Test Invite", - "password": "Test@invite", - "invite_code": frappe.db.get_value("Invite Request", {"invite_email": "test_invite@example.com"}, "name") - } + update_invite(data) + invite = frappe.db.get_value( + "Invite Request", + filters={"invite_email": "test_invite@example.com"}, + fieldname=[ + "invite_email", + "status", + "signup_email", + "full_name", + "username", + "invite_code", + "name", + ], + as_dict=True, + ) + self.assertEqual(invite.signup_email, "test_invite@example.com") + self.assertEqual(invite.full_name, "Test Invite") + self.assertEqual(invite.username, "test_invite") + self.assertEqual(invite.invite_code, invite.name) + self.assertEqual(invite.status, "Registered") - update_invite(data) - invite = frappe.db.get_value("Invite Request", - filters={"invite_email": "test_invite@example.com"}, - fieldname=["invite_email", "status", "signup_email", "full_name", "username", "invite_code", "name"], - as_dict=True) - self.assertEqual(invite.signup_email, "test_invite@example.com") - self.assertEqual(invite.full_name, "Test Invite") - self.assertEqual(invite.username, "test_invite") - self.assertEqual(invite.invite_code, invite.name) - self.assertEqual(invite.status, "Registered") + user = frappe.db.get_value( + "User", + "test_invite@example.com", + fieldname=["first_name", "username", "send_welcome_email", "user_type"], + as_dict=True, + ) + self.assertTrue(user) + self.assertEqual(user.first_name, invite.full_name.split(" ")[0]) + self.assertEqual(user.username, invite.username) + self.assertEqual(user.send_welcome_email, 0) + self.assertEqual(user.user_type, "Website User") - user = frappe.db.get_value("User", "test_invite@example.com", - fieldname=["first_name", "username", "send_welcome_email", "user_type"], - as_dict=True) - self.assertTrue(user) - self.assertEqual(user.first_name, invite.full_name.split(" ")[0]) - self.assertEqual(user.username, invite.username) - self.assertEqual(user.send_welcome_email, 0) - self.assertEqual(user.user_type, "Website User") + @classmethod + def tearDownClass(self): + if frappe.db.exists("User", "test_invite@example.com"): + frappe.delete_doc("User", "test_invite@example.com") - @classmethod - def tearDownClass(self): - if frappe.db.exists("User", "test_invite@example.com"): - frappe.delete_doc("User", "test_invite@example.com") - - invite_request = frappe.db.exists("Invite Request", {"invite_email": "test_invite@example.com"}) - if invite_request: - frappe.delete_doc("Invite Request", invite_request) + invite_request = frappe.db.exists( + "Invite Request", {"invite_email": "test_invite@example.com"} + ) + if invite_request: + frappe.delete_doc("Invite Request", invite_request) diff --git a/lms/lms/doctype/lesson_assignment/lesson_assignment.js b/lms/lms/doctype/lesson_assignment/lesson_assignment.js index 9ec731c4..b771ba8b 100644 --- a/lms/lms/doctype/lesson_assignment/lesson_assignment.js +++ b/lms/lms/doctype/lesson_assignment/lesson_assignment.js @@ -1,8 +1,7 @@ // Copyright (c) 2021, Frappe and contributors // For license information, please see license.txt -frappe.ui.form.on('Lesson Assignment', { - // refresh: function(frm) { - - // } +frappe.ui.form.on("Lesson Assignment", { + // refresh: function(frm) { + // } }); diff --git a/lms/lms/doctype/lesson_assignment/lesson_assignment.py b/lms/lms/doctype/lesson_assignment/lesson_assignment.py index 81d550b2..8c61faa2 100644 --- a/lms/lms/doctype/lesson_assignment/lesson_assignment.py +++ b/lms/lms/doctype/lesson_assignment/lesson_assignment.py @@ -2,48 +2,51 @@ # For license information, please see license.txt import frappe -from frappe.model.document import Document from frappe import _ +from frappe.model.document import Document class LessonAssignment(Document): - def validate(self): - self.validate_duplicates() + def validate(self): + self.validate_duplicates() - - def validate_duplicates(self): - if frappe.db.exists("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)) + def validate_duplicates(self): + if frappe.db.exists( + "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 + ) + ) @frappe.whitelist() def upload_assignment(assignment, lesson): - args = { - "doctype": "Lesson Assignment", - "lesson": lesson, - "member": frappe.session.user - } - if frappe.db.exists(args): - del args["doctype"] - frappe.db.set_value("Lesson Assignment", args, "assignment", assignment) - else: - args.update({"assignment": assignment}) - lesson_work = frappe.get_doc(args) - lesson_work.save(ignore_permissions=True) + args = { + "doctype": "Lesson Assignment", + "lesson": lesson, + "member": frappe.session.user, + } + if frappe.db.exists(args): + del args["doctype"] + frappe.db.set_value("Lesson Assignment", args, "assignment", assignment) + else: + args.update({"assignment": assignment}) + lesson_work = frappe.get_doc(args) + lesson_work.save(ignore_permissions=True) @frappe.whitelist() def get_assignment(lesson): - assignment = frappe.db.get_value("Lesson Assignment", { - "lesson": lesson, - "member": frappe.session.user - }, ["lesson", "member", "assignment"], - as_dict=True) - assignment.file_name = frappe.db.get_value("File", {"file_url": assignment.assignment}, "file_name") - return assignment - - - - - + assignment = frappe.db.get_value( + "Lesson Assignment", + {"lesson": lesson, "member": frappe.session.user}, + ["lesson", "member", "assignment"], + as_dict=True, + ) + assignment.file_name = frappe.db.get_value( + "File", {"file_url": assignment.assignment}, "file_name" + ) + return assignment diff --git a/lms/lms/doctype/lesson_assignment/test_lesson_assignment.py b/lms/lms/doctype/lesson_assignment/test_lesson_assignment.py index 9602c016..b8792556 100644 --- a/lms/lms/doctype/lesson_assignment/test_lesson_assignment.py +++ b/lms/lms/doctype/lesson_assignment/test_lesson_assignment.py @@ -4,5 +4,6 @@ # import frappe import unittest + class TestLessonAssignment(unittest.TestCase): pass diff --git a/lms/lms/doctype/lesson_reference/lesson_reference.py b/lms/lms/doctype/lesson_reference/lesson_reference.py index 749129d3..972708c2 100644 --- a/lms/lms/doctype/lesson_reference/lesson_reference.py +++ b/lms/lms/doctype/lesson_reference/lesson_reference.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class LessonReference(Document): pass diff --git a/lms/lms/doctype/lms_batch/lms_batch.js b/lms/lms/doctype/lms_batch/lms_batch.js index 73005977..658a67ed 100644 --- a/lms/lms/doctype/lms_batch/lms_batch.js +++ b/lms/lms/doctype/lms_batch/lms_batch.js @@ -1,8 +1,7 @@ // Copyright (c) 2021, FOSS United and contributors // For license information, please see license.txt -frappe.ui.form.on('LMS Batch', { - // refresh: function(frm) { - - // } +frappe.ui.form.on("LMS Batch", { + // refresh: function(frm) { + // } }); diff --git a/lms/lms/doctype/lms_batch/lms_batch.py b/lms/lms/doctype/lms_batch/lms_batch.py index d360f5be..57a0827c 100644 --- a/lms/lms/doctype/lms_batch/lms_batch.py +++ b/lms/lms/doctype/lms_batch/lms_batch.py @@ -1,101 +1,94 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2021, FOSS United and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe -from frappe.model.document import Document from frappe import _ -from lms.lms.doctype.lms_batch_membership.lms_batch_membership import create_membership +from frappe.model.document import Document + +from lms.lms.doctype.lms_batch_membership.lms_batch_membership import \ + create_membership from lms.lms.utils import is_mentor + class LMSBatch(Document): - def validate(self): - pass - #self.validate_if_mentor() + def validate(self): + pass + # self.validate_if_mentor() - def validate_if_mentor(self): - if not is_mentor(self.course, frappe.session.user): - 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)) + def validate_if_mentor(self): + if not is_mentor(self.course, frappe.session.user): + 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)) - def after_insert(self): - create_membership(batch=self.name, course=self.course, member_type="Mentor") + def after_insert(self): + create_membership(batch=self.name, course=self.course, member_type="Mentor") - def is_member(self, email, member_type=None): - """Checks if a person is part of a batch. + def is_member(self, email, member_type=None): + """Checks if a person is part of a batch. - If member_type is specified, checks if the person is a Student/Mentor. - """ + If member_type is specified, checks if the person is a Student/Mentor. + """ - filters = { - "batch": self.name, - "member": email - } - if member_type: - filters['member_type'] = member_type - return frappe.db.exists("LMS Batch Membership", filters) + filters = {"batch": self.name, "member": email} + if member_type: + filters["member_type"] = member_type + return frappe.db.exists("LMS Batch Membership", filters) + def get_membership(self, email): + """Returns the membership document of given user.""" + name = frappe.get_value( + doctype="LMS Batch Membership", + filters={"batch": self.name, "member": email}, + fieldname="name", + ) + return frappe.get_doc("LMS Batch Membership", name) - def get_membership(self, email): - """Returns the membership document of given user. - """ - name = frappe.get_value( - doctype="LMS Batch Membership", - filters={ - "batch": self.name, - "member": email - }, - fieldname="name") - return frappe.get_doc("LMS Batch Membership", name) + def get_current_lesson(self, user): + """Returns the name of the current lesson for the given user.""" + membership = self.get_membership(user) + return membership and membership.current_lesson - def get_current_lesson(self, user): - """Returns the name of the current lesson for the given user. - """ - membership = self.get_membership(user) - return membership and membership.current_lesson @frappe.whitelist() def save_message(message, batch): - doc = frappe.get_doc({ - "doctype": "LMS Message", - "batch": batch, - "author": frappe.session.user, - "message": message - }) - doc.save(ignore_permissions=True) + doc = frappe.get_doc( + { + "doctype": "LMS Message", + "batch": batch, + "author": frappe.session.user, + "message": message, + } + ) + doc.save(ignore_permissions=True) + def switch_batch(course_name, email, batch_name): - """Switches the user from the current batch of the course to a new batch. - """ - membership = frappe.get_last_doc( - "LMS Batch Membership", - filters={"course": course_name, "member": email}) + """Switches the user from the current batch of the course to a new batch.""" + membership = frappe.get_last_doc( + "LMS Batch Membership", filters={"course": course_name, "member": email} + ) - batch = frappe.get_doc("LMS Batch", batch_name) - if not batch: - raise ValueError(f"Invalid Batch: {batch_name}") + batch = frappe.get_doc("LMS Batch", batch_name) + if not batch: + raise ValueError(f"Invalid Batch: {batch_name}") - if batch.course != course_name: - raise ValueError("Can not switch batches across courses") + if batch.course != course_name: + raise ValueError("Can not switch batches across courses") - if batch.is_member(email): - print(f"{email} is already a member of {batch.title}") - return + if batch.is_member(email): + print(f"{email} is already a member of {batch.title}") + return - old_batch = frappe.get_doc("LMS Batch", membership.batch) + old_batch = frappe.get_doc("LMS Batch", membership.batch) - print("updating membership", membership.name) - membership.batch = batch_name - membership.save() + print("updating membership", membership.name) + membership.batch = batch_name + membership.save() - # update exercise submissions - filters = { - "owner": email, - "batch": old_batch.name - } - for name in frappe.db.get_all("Exercise Submission", filters=filters, pluck='name'): - doc = frappe.get_doc("Exercise Submission", name) - print("updating exercise submission", name) - doc.batch = batch_name - doc.save() + # update exercise submissions + filters = {"owner": email, "batch": old_batch.name} + for name in frappe.db.get_all("Exercise Submission", filters=filters, pluck="name"): + doc = frappe.get_doc("Exercise Submission", name) + print("updating exercise submission", name) + doc.batch = batch_name + doc.save() diff --git a/lms/lms/doctype/lms_batch/test_lms_batch.py b/lms/lms/doctype/lms_batch/test_lms_batch.py index df19a696..00083448 100644 --- a/lms/lms/doctype/lms_batch/test_lms_batch.py +++ b/lms/lms/doctype/lms_batch/test_lms_batch.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2021, FOSS United and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestLMSBatch(unittest.TestCase): pass diff --git a/lms/lms/doctype/lms_batch_membership/lms_batch_membership.js b/lms/lms/doctype/lms_batch_membership/lms_batch_membership.js index 68ac4f55..0a74cabc 100644 --- a/lms/lms/doctype/lms_batch_membership/lms_batch_membership.js +++ b/lms/lms/doctype/lms_batch_membership/lms_batch_membership.js @@ -1,14 +1,14 @@ // Copyright (c) 2021, FOSS United and contributors // For license information, please see license.txt -frappe.ui.form.on('LMS Batch Membership', { - onload: function(frm) { - frm.set_query('member', function(doc) { - return { - filters: { - "ignore_user_type": 1, - } - }; - }); - } +frappe.ui.form.on("LMS Batch Membership", { + onload: function (frm) { + frm.set_query("member", function (doc) { + return { + filters: { + ignore_user_type: 1, + }, + }; + }); + }, }); diff --git a/lms/lms/doctype/lms_batch_membership/lms_batch_membership.py b/lms/lms/doctype/lms_batch_membership/lms_batch_membership.py index 5e975ce0..6f8e303c 100644 --- a/lms/lms/doctype/lms_batch_membership/lms_batch_membership.py +++ b/lms/lms/doctype/lms_batch_membership/lms_batch_membership.py @@ -1,78 +1,90 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2021, FOSS United and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe -from frappe.model.document import Document from frappe import _ +from frappe.model.document import Document + class LMSBatchMembership(Document): + def validate(self): + self.validate_membership_in_same_batch() + self.validate_membership_in_different_batch_same_course() - def validate(self): - self.validate_membership_in_same_batch() - self.validate_membership_in_different_batch_same_course() + def validate_membership_in_same_batch(self): + filters = {"member": self.member, "course": self.course, "name": ["!=", self.name]} + 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): - filters={ - "member": self.member, - "course": self.course, - "name": ["!=", self.name] - } - 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: + member_name = frappe.db.get_value("User", self.member, "full_name") + course_title = frappe.db.get_value("LMS Course", self.course, "title") + frappe.throw( + _("{0} is already a {1} of the course {2}").format( + member_name, previous_membership.member_type, course_title + ) + ) - if previous_membership: - member_name = frappe.db.get_value("User", self.member, "full_name") - course_title = frappe.db.get_value("LMS Course", self.course, "title") - frappe.throw(_("{0} is already a {1} of the course {2}").format(member_name, previous_membership.member_type, course_title)) + def validate_membership_in_different_batch_same_course(self): + """Ensures that a studnet is only part of one batch.""" + # nothing to worry if the member is not a student + if self.member_type != "Student": + return - def validate_membership_in_different_batch_same_course(self): - """Ensures that a studnet is only part of one batch. - """ - # nothing to worry if the member is not a student - if self.member_type != "Student": - return + course = frappe.db.get_value("LMS Batch", self.batch, "course") + memberships = frappe.get_all( + "LMS Batch Membership", + filters={ + "member": self.member, + "name": ["!=", self.name], + "member_type": "Student", + "course": self.course, + }, + fields=["batch", "member_type", "name"], + ) - course = frappe.db.get_value("LMS Batch", self.batch, "course") - memberships = frappe.get_all( - "LMS Batch Membership", - filters={ - "member": self.member, - "name": ["!=", self.name], - "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 + ) + ) - 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() -def create_membership(course, batch=None, member=None, member_type="Student", role="Member"): - frappe.get_doc({ - "doctype": "LMS Batch Membership", - "batch": batch, - "course": course, - "role": role, - "member_type": member_type, - "member": member or frappe.session.user - }).save(ignore_permissions=True) - return "OK" +def create_membership( + course, batch=None, member=None, member_type="Student", role="Member" +): + frappe.get_doc( + { + "doctype": "LMS Batch Membership", + "batch": batch, + "course": course, + "role": role, + "member_type": member_type, + "member": member or frappe.session.user, + } + ).save(ignore_permissions=True) + return "OK" + @frappe.whitelist() def update_current_membership(batch, course, member): - all_memberships = frappe.get_all("LMS Batch Membership", {"member": member, "course": course}) - for membership in all_memberships: - frappe.db.set_value("LMS Batch Membership", membership.name, "is_current", 0) + all_memberships = frappe.get_all( + "LMS Batch Membership", {"member": member, "course": course} + ) + 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}) - if len(current_membership): - frappe.db.set_value("LMS Batch Membership", current_membership[0].name, "is_current", 1) + current_membership = frappe.get_all( + "LMS Batch Membership", {"batch": batch, "member": member} + ) + if len(current_membership): + frappe.db.set_value( + "LMS Batch Membership", current_membership[0].name, "is_current", 1 + ) diff --git a/lms/lms/doctype/lms_batch_membership/test_lms_batch_membership.py b/lms/lms/doctype/lms_batch_membership/test_lms_batch_membership.py index 19604303..71e0f5eb 100644 --- a/lms/lms/doctype/lms_batch_membership/test_lms_batch_membership.py +++ b/lms/lms/doctype/lms_batch_membership/test_lms_batch_membership.py @@ -1,62 +1,67 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2021, FOSS United and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest 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): - def setUp(self): - frappe.db.sql("DELETE FROM `tabLMS Batch Membership`") - frappe.db.sql("DELETE FROM `tabLMS Batch`") - frappe.db.sql('delete from `tabLMS Course Mentor Mapping`') - frappe.db.sql("DELETE FROM `tabUser` where email like '%@test.com'") + def setUp(self): + frappe.db.sql("DELETE FROM `tabLMS Batch Membership`") + frappe.db.sql("DELETE FROM `tabLMS Batch`") + frappe.db.sql("delete from `tabLMS Course Mentor Mapping`") + frappe.db.sql("DELETE FROM `tabUser` where email like '%@test.com'") - def new_course_batch(self): - course = new_course("Test Course") + def new_course_batch(self): + course = new_course("Test Course") - new_user("Test Mentor", "mentor@test.com") - # without this, the creating batch will fail - course.add_mentor("mentor@test.com") + new_user("Test Mentor", "mentor@test.com") + # without this, the creating batch will fail + course.add_mentor("mentor@test.com") - frappe.session.user = "mentor@test.com" + frappe.session.user = "mentor@test.com" - batch = frappe.get_doc({ - "doctype": "LMS Batch", - "name": "test-batch", - "title": "Test Batch", - "course": course.name - }) - batch.insert(ignore_permissions=True) + batch = frappe.get_doc( + { + "doctype": "LMS Batch", + "name": "test-batch", + "title": "Test Batch", + "course": course.name, + } + ) + batch.insert(ignore_permissions=True) - frappe.session.user = "Administrator" - return course, batch + frappe.session.user = "Administrator" + return course, batch - def add_membership(self, batch_name, member_name, member_type="Student"): - doc = frappe.get_doc({ - "doctype": "LMS Batch Membership", - "batch": batch_name, - "member": member_name, - "member_type": member_type - }) - doc.insert() - return doc + def add_membership(self, batch_name, member_name, member_type="Student"): + doc = frappe.get_doc( + { + "doctype": "LMS Batch Membership", + "batch": batch_name, + "member": member_name, + "member_type": member_type, + } + ) + doc.insert() + return doc - def test_membership(self): - course, batch = self.new_course_batch() - member = new_user("Test", "test01@test.com") - membership = self.add_membership(batch.name, member.name) + def test_membership(self): + course, batch = self.new_course_batch() + member = new_user("Test", "test01@test.com") + membership = self.add_membership(batch.name, member.name) - assert membership.course == course.name - assert membership.member_name == member.full_name + assert membership.course == course.name + assert membership.member_name == member.full_name - def test_membership_change_role(self): - course, batch = self.new_course_batch() - member = new_user("Test", "test01@test.com") - membership = self.add_membership(batch.name, member.name) + def test_membership_change_role(self): + course, batch = self.new_course_batch() + member = new_user("Test", "test01@test.com") + membership = self.add_membership(batch.name, member.name) - # it should be possible to change role - membership.role = "Admin" - membership.save() + # it should be possible to change role + membership.role = "Admin" + membership.save() diff --git a/lms/lms/doctype/lms_certificate/lms_certificate.js b/lms/lms/doctype/lms_certificate/lms_certificate.js index 8d6f5e12..0e5a5162 100644 --- a/lms/lms/doctype/lms_certificate/lms_certificate.js +++ b/lms/lms/doctype/lms_certificate/lms_certificate.js @@ -1,17 +1,21 @@ // Copyright (c) 2021, FOSS United and contributors // For license information, please see license.txt -frappe.ui.form.on('LMS Certificate', { +frappe.ui.form.on("LMS Certificate", { onload: (frm) => { frm.set_query("member", function (doc) { - return { - filters: { - "ignore_user_type": 1, - } - }; + return { + filters: { + ignore_user_type: 1, + }, + }; }); }, 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" + ); + }, }); diff --git a/lms/lms/doctype/lms_certificate/lms_certificate.py b/lms/lms/doctype/lms_certificate/lms_certificate.py index 2467fff9..3c5f4de8 100644 --- a/lms/lms/doctype/lms_certificate/lms_certificate.py +++ b/lms/lms/doctype/lms_certificate/lms_certificate.py @@ -2,49 +2,55 @@ # For license information, please see license.txt import frappe -from frappe.model.document import Document -from frappe.utils import nowdate, add_years from frappe import _ +from frappe.model.document import Document +from frappe.utils import add_years, nowdate from frappe.utils.pdf import get_pdf + from lms.lms.utils import is_certified -class LMSCertificate(Document): - def before_insert(self): - certificates = frappe.get_all("LMS Certificate", { - "member": self.member, - "course": self.course - }) - if len(certificates): - full_name = frappe.db.get_value("User", self.member, "full_name") - 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)) +class LMSCertificate(Document): + def before_insert(self): + certificates = frappe.get_all( + "LMS Certificate", {"member": self.member, "course": self.course} + ) + if len(certificates): + full_name = frappe.db.get_value("User", self.member, "full_name") + 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.whitelist() def create_certificate(course): - certificate = is_certified(course) + certificate = is_certified(course) - if certificate: - return certificate + if certificate: + return certificate - else: - expires_after_yrs = int(frappe.db.get_value("LMS Course", course, "expiry")) - expiry_date = None - if expires_after_yrs: - expiry_date = add_years(nowdate(), expires_after_yrs) + else: + expires_after_yrs = int(frappe.db.get_value("LMS Course", course, "expiry")) + expiry_date = None + if 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() def get_certificate_pdf(html): - frappe.local.response.filename = "certificate.pdf" - frappe.local.response.filecontent = get_pdf(html, {"orientation": "LandScape"}) - frappe.local.response.type = "pdf" + frappe.local.response.filename = "certificate.pdf" + frappe.local.response.filecontent = get_pdf(html, {"orientation": "LandScape"}) + frappe.local.response.type = "pdf" diff --git a/lms/lms/doctype/lms_certificate/test_lms_certificate.py b/lms/lms/doctype/lms_certificate/test_lms_certificate.py index 181aabf2..f7754e43 100644 --- a/lms/lms/doctype/lms_certificate/test_lms_certificate.py +++ b/lms/lms/doctype/lms_certificate/test_lms_certificate.py @@ -1,25 +1,24 @@ # Copyright (c) 2021, FOSS United and Contributors # See license.txt -import frappe 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 frappe.utils import nowdate, add_years, cint +from lms.lms.doctype.lms_course.test_lms_course import new_course + 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): - course = new_course("Test Certificate", { - "enable_certification": 1, - "expiry": 2 - }) - certificate = create_certificate(course.name) + self.assertEqual(certificate.member, "Administrator") + self.assertEqual(certificate.course, course.name) + self.assertEqual(certificate.issue_date, nowdate()) + self.assertEqual(certificate.expiry_date, add_years(nowdate(), cint(course.expiry))) - self.assertEqual(certificate.member, "Administrator") - self.assertEqual(certificate.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) + frappe.db.delete("LMS Certificate", certificate.name) + frappe.db.delete("LMS Course", course.name) diff --git a/lms/lms/doctype/lms_certificate_evaluation/lms_certificate_evaluation.js b/lms/lms/doctype/lms_certificate_evaluation/lms_certificate_evaluation.js index 40de133d..21515f0e 100644 --- a/lms/lms/doctype/lms_certificate_evaluation/lms_certificate_evaluation.js +++ b/lms/lms/doctype/lms_certificate_evaluation/lms_certificate_evaluation.js @@ -1,34 +1,34 @@ // Copyright (c) 2022, Frappe and contributors // For license information, please see license.txt -frappe.ui.form.on('LMS Certificate Evaluation', { - refresh: function(frm) { +frappe.ui.form.on("LMS Certificate Evaluation", { + refresh: function (frm) { if (frm.doc.status == "Pass") { frm.add_custom_button(__("Create LMS Certificate"), () => { frappe.model.open_mapped_doc({ method: "lms.lms.doctype.lms_certificate_evaluation.lms_certificate_evaluation.create_lms_certificate", - frm: frm + frm: frm, }); }); } }, - onload: function(frm) { - frm.set_query("course", function(doc) { + onload: function (frm) { + frm.set_query("course", function (doc) { return { filters: { - "enable_certification": true, - "grant_certificate_after": "Evaluation" - } + enable_certification: true, + grant_certificate_after: "Evaluation", + }, }; }); - frm.set_query('member', function(doc) { + frm.set_query("member", function (doc) { return { filters: { - "ignore_user_type": 1, - } + ignore_user_type: 1, + }, }; }); - } + }, }); diff --git a/lms/lms/doctype/lms_certificate_evaluation/lms_certificate_evaluation.py b/lms/lms/doctype/lms_certificate_evaluation/lms_certificate_evaluation.py index 5cb730f6..3567982d 100644 --- a/lms/lms/doctype/lms_certificate_evaluation/lms_certificate_evaluation.py +++ b/lms/lms/doctype/lms_certificate_evaluation/lms_certificate_evaluation.py @@ -5,14 +5,17 @@ import frappe from frappe.model.document import Document from frappe.model.mapper import get_mapped_doc + class LMSCertificateEvaluation(Document): - pass + pass + @frappe.whitelist() def create_lms_certificate(source_name, target_doc=None): - doc = get_mapped_doc("LMS Certificate Evaluation", source_name, { - "LMS Certificate Evaluation": { - "doctype": "LMS Certificate" - } - }, target_doc) - return doc + doc = get_mapped_doc( + "LMS Certificate Evaluation", + source_name, + {"LMS Certificate Evaluation": {"doctype": "LMS Certificate"}}, + target_doc, + ) + return doc diff --git a/lms/lms/doctype/lms_certificate_request/lms_certificate_request.js b/lms/lms/doctype/lms_certificate_request/lms_certificate_request.js index 24d6ef31..84567f0d 100644 --- a/lms/lms/doctype/lms_certificate_request/lms_certificate_request.js +++ b/lms/lms/doctype/lms_certificate_request/lms_certificate_request.js @@ -1,23 +1,23 @@ // Copyright (c) 2022, Frappe and contributors // For license information, please see license.txt -frappe.ui.form.on('LMS Certificate Request', { - refresh: function(frm) { +frappe.ui.form.on("LMS Certificate Request", { + refresh: function (frm) { frm.add_custom_button(__("Create LMS Certificate Evaluation"), () => { frappe.model.open_mapped_doc({ method: "lms.lms.doctype.lms_certificate_request.lms_certificate_request.create_lms_certificate_evaluation", - frm: frm + frm: frm, }); }); }, - onload: function(frm) { - frm.set_query('member', function(doc) { + onload: function (frm) { + frm.set_query("member", function (doc) { return { filters: { - "ignore_user_type": 1, - } + ignore_user_type: 1, + }, }; }); - } + }, }); diff --git a/lms/lms/doctype/lms_certificate_request/lms_certificate_request.py b/lms/lms/doctype/lms_certificate_request/lms_certificate_request.py index ebe39caf..0e7b20eb 100644 --- a/lms/lms/doctype/lms_certificate_request/lms_certificate_request.py +++ b/lms/lms/doctype/lms_certificate_request/lms_certificate_request.py @@ -2,54 +2,61 @@ # For license information, please see license.txt import frappe +from frappe import _ from frappe.model.document import Document from frappe.model.mapper import get_mapped_doc -from frappe import _ -from frappe.utils import getdate, format_date, format_time +from frappe.utils import format_date, format_time, getdate + class LMSCertificateRequest(Document): + def validate(self): + self.validate_if_existing_requests() - def validate(self): - self.validate_if_existing_requests() + def validate_if_existing_requests(self): + existing_requests = frappe.get_all( + "LMS Certificate Request", + {"member": self.member, "course": self.course}, + ["date", "start_time", "course"], + ) - def validate_if_existing_requests(self): - existing_requests = frappe.get_all("LMS Certificate Request", { - "member": self.member, - "course": self.course - }, ["date", "start_time", "course"]) + 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}." + ) + ) - 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() def create_certificate_request(course, date, day, start_time, end_time): - is_member = frappe.db.exists({ - "doctype": "LMS Batch Membership", - "course": course, - "member": frappe.session.user - }) + is_member = frappe.db.exists( + {"doctype": "LMS Batch Membership", "course": course, "member": frappe.session.user} + ) - if not is_member: - return + if not is_member: + return - frappe.get_doc({ - "doctype": "LMS Certificate Request", - "course": course, - "member": frappe.session.user, - "date": date, - "day": day, - "start_time": start_time, - "end_time": end_time - }).save(ignore_permissions=True) + frappe.get_doc( + { + "doctype": "LMS Certificate Request", + "course": course, + "member": frappe.session.user, + "date": date, + "day": day, + "start_time": start_time, + "end_time": end_time, + } + ).save(ignore_permissions=True) @frappe.whitelist() def create_lms_certificate_evaluation(source_name, target_doc=None): - doc = get_mapped_doc("LMS Certificate Request", source_name, { - "LMS Certificate Request": { - "doctype": "LMS Certificate Evaluation" - } - }, target_doc) - return doc + doc = get_mapped_doc( + "LMS Certificate Request", + source_name, + {"LMS Certificate Request": {"doctype": "LMS Certificate Evaluation"}}, + target_doc, + ) + return doc diff --git a/lms/lms/doctype/lms_course/lms_course.js b/lms/lms/doctype/lms_course/lms_course.js index c08e1683..20d6a0fd 100644 --- a/lms/lms/doctype/lms_course/lms_course.js +++ b/lms/lms/doctype/lms_course/lms_course.js @@ -1,33 +1,30 @@ // Copyright (c) 2021, FOSS United and contributors // 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, - } + course: frm.doc.name, + }, }; }); frm.set_query("instructor", "instructors", function () { return { filters: { - "ignore_user_type": 1, - } + ignore_user_type: 1, + }, }; }); frm.set_query("course", "related_courses", function () { return { filters: { - "published": true, - } + published: true, + }, }; }); - - } + }, }); diff --git a/lms/lms/doctype/lms_course/lms_course.py b/lms/lms/doctype/lms_course/lms_course.py index 62ab9300..a420dec3 100644 --- a/lms/lms/doctype/lms_course/lms_course.py +++ b/lms/lms/doctype/lms_course/lms_course.py @@ -1,307 +1,313 @@ # Copyright (c) 2021, Frappe and contributors # For license information, please see license.txt -from __future__ import unicode_literals +import json + import frappe from frappe.model.document import Document -import json -from ...utils import generate_slug, validate_image from frappe.utils import cint + from lms.lms.utils import get_chapters +from ...utils import generate_slug, validate_image + 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): - self.validate_instructors() - self.validate_status() - self.image = validate_image(self.image) + def validate_status(self): + if self.published: + self.status = "Approved" + def on_update(self): + if not self.upcoming and self.has_value_changed("upcoming"): + self.send_email_to_interested_users() - 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 send_email_to_interested_users(self): + interested_users = frappe.get_all( + "LMS Course Interest", {"course": self.name}, ["name", "user"] + ) + subject = self.title + " is available!" + args = { + "title": self.title, + "course_link": f"/courses/{self.name}", + "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): - if self.published: - self.status = "Approved" + def autoname(self): + if not self.name: + self.name = generate_slug(self.title, "LMS Course") + def __repr__(self): + return f"" - def on_update(self): - if not self.upcoming and self.has_value_changed("upcoming"): - self.send_email_to_interested_users() + def has_mentor(self, email): + """Checks if this course has a mentor with given email.""" + 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): - interested_users = frappe.get_all("LMS Course Interest", { - "course": self.name - }, - ["name", "user"]) - subject = self.title + " is available!" - 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() - } + def add_mentor(self, email): + """Adds a new mentor to the course.""" + if not email: + raise ValueError("Invalid email") + if email == "Guest": + raise ValueError("Guest user can not be added as a mentor") - 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) + # given user is already a mentor + if self.has_mentor(email): + return + doc = frappe.get_doc( + {"doctype": "LMS Course Mentor Mapping", "course": self.name, "mentor": email} + ) + doc.insert() - def autoname(self): - if not self.name: - self.name = generate_slug(self.title, "LMS Course") + 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 - def __repr__(self): - return f"" + 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 has_mentor(self, email): - """Checks if this course has a mentor with given email. - """ - if not email or email == "Guest": - return False + def get_cohorts(self): + return frappe.get_all( + "Cohort", + {"course": self.name}, + ["name", "slug", "title", "begin_date", "end_date"], + order_by="creation", + ) - mapping = frappe.get_all("LMS Course Mentor Mapping", {"course": self.name, "mentor": email}) - return mapping != [] + 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 add_mentor(self, email): - """Adds a new mentor to the course. - """ - if not email: - raise ValueError("Invalid email") - if email == "Guest": - raise ValueError("Guest user can not be added as a mentor") + 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 - # given user is already a mentor - if self.has_mentor(email): - return - - doc = frappe.get_doc({ - "doctype": "LMS Course Mentor Mapping", - "course": self.name, - "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 + 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() def reindex_exercises(doc): - course_data = json.loads(doc) - course = frappe.get_doc("LMS Course", course_data['name']) - course.reindex_exercises() - frappe.msgprint("All exercises in this course have been re-indexed.") + course_data = json.loads(doc) + course = frappe.get_doc("LMS Course", course_data["name"]) + course.reindex_exercises() + frappe.msgprint("All exercises in this course have been re-indexed.") @frappe.whitelist(allow_guest=True) def search_course(text): - search_courses = [] - courses = frappe.get_all("LMS Course", - filters= { - "published": True - }, - or_filters = { - "title": ["like", "%{0}%".format(text)], - "tags": ["like", "%{0}%".format(text)], - "short_introduction": ["like", "%{0}%".format(text)], - "description": ["like", "%{0}%".format(text)], - }) + search_courses = [] + courses = frappe.get_all( + "LMS Course", + filters={"published": True}, + or_filters={ + "title": ["like", f"%{text}%"], + "tags": ["like", f"%{text}%"], + "short_introduction": ["like", f"%{text}%"], + "description": ["like", f"%{text}%"], + }, + ) - """ for course in courses: + """ for course in courses: 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"), "courses": search_courses, "widgets": Widgets() }) """ - return courses + return courses @frappe.whitelist() def submit_for_review(course): - chapters = frappe.get_all("Chapter Reference", {"parent": course}) - if not len(chapters): - return "No Chp" - frappe.db.set_value("LMS Course", course, "status", "Under Review") - return "OK" + chapters = frappe.get_all("Chapter Reference", {"parent": course}) + if not len(chapters): + return "No Chp" + frappe.db.set_value("LMS Course", course, "status", "Under Review") + return "OK" @frappe.whitelist() -def save_course(tags, title, short_introduction, video_link, description, course, published, upcoming, image=None): - if course: - doc = frappe.get_doc("LMS Course", course) - else: - doc = frappe.get_doc({ - "doctype": "LMS Course" - }) +def save_course( + tags, + title, + short_introduction, + video_link, + 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({ - "title": title, - "short_introduction": short_introduction, - "video_link": video_link, - "image": image, - "description": description, - "tags": tags, - "published": cint(published), - "upcoming": cint(upcoming) - }) - doc.save(ignore_permissions=True) - return doc.name + doc.update( + { + "title": title, + "short_introduction": short_introduction, + "video_link": video_link, + "image": image, + "description": description, + "tags": tags, + "published": cint(published), + "upcoming": cint(upcoming), + } + ) + doc.save(ignore_permissions=True) + return doc.name @frappe.whitelist() def save_chapter(course, title, chapter_description, idx, chapter): - if chapter: - doc = frappe.get_doc("Course Chapter", chapter) - else: - doc = frappe.get_doc({ - "doctype": "Course Chapter" - }) + if chapter: + doc = frappe.get_doc("Course Chapter", chapter) + else: + doc = frappe.get_doc({"doctype": "Course Chapter"}) - doc.update({ - "course": course, - "title": title, - "description": chapter_description - }) - doc.save(ignore_permissions=True) + doc.update({"course": course, "title": title, "description": chapter_description}) + doc.save(ignore_permissions=True) - if chapter: - chapter_reference = frappe.get_doc("Chapter Reference", {"chapter": chapter}) - else: - chapter_reference = frappe.get_doc({ - "doctype": "Chapter Reference", - "parent": course, - "parenttype": "LMS Course", - "parentfield": "chapters", - "idx": idx - }) + if chapter: + chapter_reference = frappe.get_doc("Chapter Reference", {"chapter": chapter}) + else: + chapter_reference = frappe.get_doc( + { + "doctype": "Chapter Reference", + "parent": course, + "parenttype": "LMS Course", + "parentfield": "chapters", + "idx": idx, + } + ) - chapter_reference.update({"chapter": doc.name}) - chapter_reference.save(ignore_permissions=True) + chapter_reference.update({"chapter": doc.name}) + chapter_reference.save(ignore_permissions=True) - return doc.name + return doc.name @frappe.whitelist() -def save_lesson(title, body, chapter, preview, 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" - }) +def save_lesson( + title, + body, + chapter, + preview, + 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({ - "chapter": chapter, - "title": title, - "body": body, - "include_in_preview": preview, - "youtube": youtube, - "quiz_id": quiz_id, - "question": question, - "file_type": file_type - }) - doc.save(ignore_permissions=True) + doc.update( + { + "chapter": chapter, + "title": title, + "body": body, + "include_in_preview": preview, + "youtube": youtube, + "quiz_id": quiz_id, + "question": question, + "file_type": file_type, + } + ) + doc.save(ignore_permissions=True) - if lesson: - lesson_reference = frappe.get_doc("Lesson Reference", {"lesson": lesson}) - else: - lesson_reference = frappe.get_doc({ - "doctype": "Lesson Reference", - "parent": chapter, - "parenttype": "Course Chapter", - "parentfield": "lessons", - "idx": idx - }) + if lesson: + lesson_reference = frappe.get_doc("Lesson Reference", {"lesson": lesson}) + else: + lesson_reference = frappe.get_doc( + { + "doctype": "Lesson Reference", + "parent": chapter, + "parenttype": "Course Chapter", + "parentfield": "lessons", + "idx": idx, + } + ) - lesson_reference.update({"lesson": doc.name}) - lesson_reference.save(ignore_permissions=True) + lesson_reference.update({"lesson": doc.name}) + lesson_reference.save(ignore_permissions=True) - return doc.name + return doc.name diff --git a/lms/lms/doctype/lms_course/test_lms_course.py b/lms/lms/doctype/lms_course/test_lms_course.py index ef133bda..d144e41b 100644 --- a/lms/lms/doctype/lms_course/test_lms_course.py +++ b/lms/lms/doctype/lms_course/test_lms_course.py @@ -1,93 +1,90 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2021, FOSS United and Contributors # See license.txt -from __future__ import unicode_literals + +import unittest import frappe + from .lms_course import LMSCourse -import unittest 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): - course = new_course("Test Course") - assert course.title == "Test Course" - assert course.name == "test-course" + user = new_user("Tester", "tester@example.com") + course.add_mentor("tester@example.com") + 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 _test_add_mentors(self): - course = new_course("Test Course") - assert course.get_mentors() == [] + def tearDown(self): + if frappe.db.exists("User", "tester@example.com"): + frappe.delete_doc("User", "tester@example.com") - user = new_user("Tester", "tester@example.com") - course.add_mentor("tester@example.com") - - 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}] - - - def tearDown(self): - if frappe.db.exists("User", "tester@example.com"): - 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") + 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): - user = frappe.db.exists("User", email) - if user: - return frappe.get_doc("User", user) - else: - filters = { - "doctype": "User", - "email": email, - "first_name": name, - "send_welcome_email": False - } + user = frappe.db.exists("User", email) + if user: + return frappe.get_doc("User", user) + else: + filters = { + "doctype": "User", + "email": email, + "first_name": name, + "send_welcome_email": False, + } - doc = frappe.get_doc(filters) - doc.insert() - return doc + doc = frappe.get_doc(filters) + doc.insert() + return doc def new_course(title, additional_filters=None): - course = frappe.db.exists("LMS Course", { "title": title }) - if course: - return frappe.get_doc("LMS Course", course) - else: - create_evaluator() - filters = { - "doctype": "LMS Course", - "title": title, - "short_introduction": title, - "description": title - } + course = frappe.db.exists("LMS Course", {"title": title}) + if course: + return frappe.get_doc("LMS Course", course) + else: + create_evaluator() + filters = { + "doctype": "LMS Course", + "title": title, + "short_introduction": title, + "description": title, + } - if additional_filters: - filters.update(additional_filters) + if additional_filters: + filters.update(additional_filters) - doc = frappe.get_doc(filters) - doc.insert(ignore_permissions=True) - return doc + doc = frappe.get_doc(filters) + doc.insert(ignore_permissions=True) + return doc def create_evaluator(): - if not frappe.db.exists("Course Evaluator", "evaluator@example.com"): - new_user("Evaluator", "evaluator@example.com") - frappe.get_doc({ - "doctype": "Course Evaluator", - "evaluator": "evaluator@example.com" - }).save(ignore_permissions=True) + if not frappe.db.exists("Course Evaluator", "evaluator@example.com"): + new_user("Evaluator", "evaluator@example.com") + frappe.get_doc( + {"doctype": "Course Evaluator", "evaluator": "evaluator@example.com"} + ).save(ignore_permissions=True) diff --git a/lms/lms/doctype/lms_course_enrollment/lms_course_enrollment.js b/lms/lms/doctype/lms_course_enrollment/lms_course_enrollment.js index 59edba94..39a5b3f7 100644 --- a/lms/lms/doctype/lms_course_enrollment/lms_course_enrollment.js +++ b/lms/lms/doctype/lms_course_enrollment/lms_course_enrollment.js @@ -1,8 +1,7 @@ // Copyright (c) 2021, FOSS United and contributors // For license information, please see license.txt -frappe.ui.form.on('LMS Course Enrollment', { - // refresh: function(frm) { - - // } +frappe.ui.form.on("LMS Course Enrollment", { + // refresh: function(frm) { + // } }); diff --git a/lms/lms/doctype/lms_course_enrollment/lms_course_enrollment.py b/lms/lms/doctype/lms_course_enrollment/lms_course_enrollment.py index d7b726ec..ffe29ea2 100644 --- a/lms/lms/doctype/lms_course_enrollment/lms_course_enrollment.py +++ b/lms/lms/doctype/lms_course_enrollment/lms_course_enrollment.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2021, FOSS United and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document + class LMSCourseEnrollment(Document): pass diff --git a/lms/lms/doctype/lms_course_enrollment/test_lms_course_enrollment.py b/lms/lms/doctype/lms_course_enrollment/test_lms_course_enrollment.py index 9a7813cb..b52ed9ca 100644 --- a/lms/lms/doctype/lms_course_enrollment/test_lms_course_enrollment.py +++ b/lms/lms/doctype/lms_course_enrollment/test_lms_course_enrollment.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2021, FOSS United and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestLMSCourseEnrollment(unittest.TestCase): pass diff --git a/lms/lms/doctype/lms_course_interest/lms_course_interest.js b/lms/lms/doctype/lms_course_interest/lms_course_interest.js index ff469e10..296e0914 100644 --- a/lms/lms/doctype/lms_course_interest/lms_course_interest.js +++ b/lms/lms/doctype/lms_course_interest/lms_course_interest.js @@ -1,8 +1,7 @@ // Copyright (c) 2021, FOSS United and contributors // For license information, please see license.txt -frappe.ui.form.on('LMS Course Interest', { - // refresh: function(frm) { - - // } +frappe.ui.form.on("LMS Course Interest", { + // refresh: function(frm) { + // } }); diff --git a/lms/lms/doctype/lms_course_interest/lms_course_interest.py b/lms/lms/doctype/lms_course_interest/lms_course_interest.py index 66817f19..15f2bddc 100644 --- a/lms/lms/doctype/lms_course_interest/lms_course_interest.py +++ b/lms/lms/doctype/lms_course_interest/lms_course_interest.py @@ -4,16 +4,18 @@ import frappe from frappe.model.document import Document + class LMSCourseInterest(Document): pass + @frappe.whitelist() def capture_interest(course): - data = { - "doctype": "LMS Course Interest", - "course": course, - "user": frappe.session.user - } - if not frappe.db.exists(data): - frappe.get_doc(data).save(ignore_permissions=True) - return "OK" + data = { + "doctype": "LMS Course Interest", + "course": course, + "user": frappe.session.user, + } + if not frappe.db.exists(data): + frappe.get_doc(data).save(ignore_permissions=True) + return "OK" diff --git a/lms/lms/doctype/lms_course_interest/test_lms_course_interest.py b/lms/lms/doctype/lms_course_interest/test_lms_course_interest.py index b5689d52..d52f8f2a 100644 --- a/lms/lms/doctype/lms_course_interest/test_lms_course_interest.py +++ b/lms/lms/doctype/lms_course_interest/test_lms_course_interest.py @@ -4,5 +4,6 @@ # import frappe import unittest + class TestLMSCourseInterest(unittest.TestCase): pass diff --git a/lms/lms/doctype/lms_course_mentor_mapping/lms_course_mentor_mapping.js b/lms/lms/doctype/lms_course_mentor_mapping/lms_course_mentor_mapping.js index c995587f..b404b1a5 100644 --- a/lms/lms/doctype/lms_course_mentor_mapping/lms_course_mentor_mapping.js +++ b/lms/lms/doctype/lms_course_mentor_mapping/lms_course_mentor_mapping.js @@ -1,14 +1,14 @@ // Copyright (c) 2021, FOSS United and contributors // For license information, please see license.txt -frappe.ui.form.on('LMS Course Mentor Mapping', { - onload: function(frm) { - frm.set_query('mentor', function(doc) { - return { - filters: { - "ignore_user_type": 1, - } - }; - }); - }, +frappe.ui.form.on("LMS Course Mentor Mapping", { + onload: function (frm) { + frm.set_query("mentor", function (doc) { + return { + filters: { + ignore_user_type: 1, + }, + }; + }); + }, }); diff --git a/lms/lms/doctype/lms_course_mentor_mapping/lms_course_mentor_mapping.py b/lms/lms/doctype/lms_course_mentor_mapping/lms_course_mentor_mapping.py index c5cc58e7..85464e09 100644 --- a/lms/lms/doctype/lms_course_mentor_mapping/lms_course_mentor_mapping.py +++ b/lms/lms/doctype/lms_course_mentor_mapping/lms_course_mentor_mapping.py @@ -1,18 +1,17 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2021, FOSS United and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe -from frappe.model.document import Document from frappe import _ +from frappe.model.document import Document + class LMSCourseMentorMapping(Document): def validate(self): - duplicate_mapping = frappe.get_all("LMS Course Mentor Mapping", - filters = { - "course": self.course, - "mentor": self.mentor - }) + duplicate_mapping = frappe.get_all( + "LMS Course Mentor Mapping", filters={"course": self.course, "mentor": self.mentor} + ) 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) + ) diff --git a/lms/lms/doctype/lms_course_mentor_mapping/test_lms_course_mentor_mapping.py b/lms/lms/doctype/lms_course_mentor_mapping/test_lms_course_mentor_mapping.py index e0ba2be5..efc18dae 100644 --- a/lms/lms/doctype/lms_course_mentor_mapping/test_lms_course_mentor_mapping.py +++ b/lms/lms/doctype/lms_course_mentor_mapping/test_lms_course_mentor_mapping.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2021, FOSS United and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestLMSCourseMentorMapping(unittest.TestCase): pass diff --git a/lms/lms/doctype/lms_course_progress/lms_course_progress.js b/lms/lms/doctype/lms_course_progress/lms_course_progress.js index 77ef3c16..2afffabe 100644 --- a/lms/lms/doctype/lms_course_progress/lms_course_progress.js +++ b/lms/lms/doctype/lms_course_progress/lms_course_progress.js @@ -1,8 +1,7 @@ // Copyright (c) 2021, FOSS United and contributors // For license information, please see license.txt -frappe.ui.form.on('LMS Course Progress', { - // refresh: function(frm) { - - // } +frappe.ui.form.on("LMS Course Progress", { + // refresh: function(frm) { + // } }); diff --git a/lms/lms/doctype/lms_course_progress/lms_course_progress.py b/lms/lms/doctype/lms_course_progress/lms_course_progress.py index 98a022b7..31bce170 100644 --- a/lms/lms/doctype/lms_course_progress/lms_course_progress.py +++ b/lms/lms/doctype/lms_course_progress/lms_course_progress.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class LMSCourseProgress(Document): pass diff --git a/lms/lms/doctype/lms_course_progress/test_lms_course_progress.py b/lms/lms/doctype/lms_course_progress/test_lms_course_progress.py index 9eabd5d3..30da4867 100644 --- a/lms/lms/doctype/lms_course_progress/test_lms_course_progress.py +++ b/lms/lms/doctype/lms_course_progress/test_lms_course_progress.py @@ -4,5 +4,6 @@ # import frappe import unittest + class TestLMSCourseProgress(unittest.TestCase): pass diff --git a/lms/lms/doctype/lms_course_review/lms_course_review.js b/lms/lms/doctype/lms_course_review/lms_course_review.js index 8382eb52..0db8771a 100644 --- a/lms/lms/doctype/lms_course_review/lms_course_review.js +++ b/lms/lms/doctype/lms_course_review/lms_course_review.js @@ -1,8 +1,7 @@ // Copyright (c) 2021, FOSS United and contributors // For license information, please see license.txt -frappe.ui.form.on('LMS Course Review', { - // refresh: function(frm) { - - // } +frappe.ui.form.on("LMS Course Review", { + // refresh: function(frm) { + // } }); diff --git a/lms/lms/doctype/lms_course_review/lms_course_review.py b/lms/lms/doctype/lms_course_review/lms_course_review.py index 8e274450..15855ba5 100644 --- a/lms/lms/doctype/lms_course_review/lms_course_review.py +++ b/lms/lms/doctype/lms_course_review/lms_course_review.py @@ -5,23 +5,19 @@ import frappe from frappe.model.document import Document from frappe.utils import cint + class LMSCourseReview(Document): pass + @frappe.whitelist() def submit_review(rating, review, course): - out_of_ratings = frappe.db.get_all("DocField", - { - "parent": "LMS Course Review", - "fieldtype": "Rating" - }, - ["options"]) - out_of_ratings = (len(out_of_ratings) and out_of_ratings[0].options) or 5 - rating = cint(rating)/out_of_ratings - frappe.get_doc({ - "doctype": "LMS Course Review", - "rating": rating, - "review": review, - "course": course - }).save(ignore_permissions=True) - return "OK" + out_of_ratings = frappe.db.get_all( + "DocField", {"parent": "LMS Course Review", "fieldtype": "Rating"}, ["options"] + ) + out_of_ratings = (len(out_of_ratings) and out_of_ratings[0].options) or 5 + rating = cint(rating) / out_of_ratings + frappe.get_doc( + {"doctype": "LMS Course Review", "rating": rating, "review": review, "course": course} + ).save(ignore_permissions=True) + return "OK" diff --git a/lms/lms/doctype/lms_course_review/test_lms_course_review.py b/lms/lms/doctype/lms_course_review/test_lms_course_review.py index da8a1450..451e4651 100644 --- a/lms/lms/doctype/lms_course_review/test_lms_course_review.py +++ b/lms/lms/doctype/lms_course_review/test_lms_course_review.py @@ -4,5 +4,6 @@ # import frappe import unittest + class TestLMSCourseReview(unittest.TestCase): pass diff --git a/lms/lms/doctype/lms_mentor_request/lms_mentor_request.js b/lms/lms/doctype/lms_mentor_request/lms_mentor_request.js index 338e39b1..cc41cf71 100644 --- a/lms/lms/doctype/lms_mentor_request/lms_mentor_request.js +++ b/lms/lms/doctype/lms_mentor_request/lms_mentor_request.js @@ -1,8 +1,7 @@ // Copyright (c) 2021, FOSS United and contributors // For license information, please see license.txt -frappe.ui.form.on('LMS Mentor Request', { - // refresh: function(frm) { - - // } +frappe.ui.form.on("LMS Mentor Request", { + // refresh: function(frm) { + // } }); diff --git a/lms/lms/doctype/lms_mentor_request/lms_mentor_request.py b/lms/lms/doctype/lms_mentor_request/lms_mentor_request.py index 6091d192..95facc91 100644 --- a/lms/lms/doctype/lms_mentor_request/lms_mentor_request.py +++ b/lms/lms/doctype/lms_mentor_request/lms_mentor_request.py @@ -1,122 +1,145 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2021, FOSS United and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe -from frappe.model.document import Document from frappe import _ +from frappe.model.document import Document + class LMSMentorRequest(Document): - def on_update(self): - if self.has_value_changed('status'): + def on_update(self): + if self.has_value_changed("status"): - if self.status == "Approved": - self.create_course_mentor_mapping() + if self.status == "Approved": + self.create_course_mentor_mapping() - if self.status != "Pending": - self.send_status_change_email() + if self.status != "Pending": + self.send_status_change_email() - def create_course_mentor_mapping(self): - mapping = frappe.get_doc({ - "doctype": "LMS Course Mentor Mapping", - "mentor": self.member, - "course": self.course - }) - mapping.save() + def create_course_mentor_mapping(self): + mapping = frappe.get_doc( + { + "doctype": "LMS Course Mentor Mapping", + "mentor": self.member, + "course": self.course, + } + ) + mapping.save() - def send_creation_email(self): - email_template = self.get_email_template('mentor_request_creation') - if not email_template: - return + def send_creation_email(self): + email_template = self.get_email_template("mentor_request_creation") + if not email_template: + return - course_details = frappe.db.get_value("LMS Course", self.course, ["owner", "slug", "title"], as_dict=True) - message = frappe.render_template(email_template.response, - { - 'member_name': frappe.db.get_value("User", frappe.session.user, "full_name"), - 'course_url': '/courses/' + course_details.slug, - 'course': course_details.title - }) + course_details = frappe.db.get_value( + "LMS Course", self.course, ["owner", "slug", "title"], as_dict=True + ) + message = frappe.render_template( + email_template.response, + { + "member_name": frappe.db.get_value("User", frappe.session.user, "full_name"), + "course_url": "/courses/" + course_details.slug, + "course": course_details.title, + }, + ) - email_args = { - "recipients": [frappe.session.user, course_details.owner], - "subject": email_template.subject, - "header": email_template.subject, - "message": message - } - frappe.enqueue(method=frappe.sendmail, queue="short", timeout=300, is_async=True, **email_args) + email_args = { + "recipients": [frappe.session.user, course_details.owner], + "subject": email_template.subject, + "header": email_template.subject, + "message": message, + } + frappe.enqueue( + method=frappe.sendmail, queue="short", timeout=300, is_async=True, **email_args + ) - def send_status_change_email(self): - email_template = self.get_email_template('mentor_request_status_update') - if not email_template: - return + def send_status_change_email(self): + email_template = self.get_email_template("mentor_request_status_update") + if not email_template: + return - course_details = frappe.db.get_value("LMS Course", self.course, ["owner", "title"], as_dict=True) - message = frappe.render_template(email_template.response, - { - 'member_name': self.member_name, - 'status': self.status, - 'course': course_details.title - }) + course_details = frappe.db.get_value( + "LMS Course", self.course, ["owner", "title"], as_dict=True + ) + message = frappe.render_template( + email_template.response, + { + "member_name": self.member_name, + "status": self.status, + "course": course_details.title, + }, + ) - if self.status == 'Approved' or self.status == 'Rejected': - email_args = { - "recipients": self.member, - "cc": [course_details.owner, self.reviewed_by], - "subject": email_template.subject, - "header": email_template.subject, - "message": message - } - frappe.enqueue(method=frappe.sendmail, queue="short", timeout=300, is_async=True, **email_args) + if self.status == "Approved" or self.status == "Rejected": + email_args = { + "recipients": self.member, + "cc": [course_details.owner, self.reviewed_by], + "subject": email_template.subject, + "header": email_template.subject, + "message": message, + } + frappe.enqueue( + method=frappe.sendmail, queue="short", timeout=300, is_async=True, **email_args + ) - elif self.status == 'Withdrawn': - email_args = { - "recipients": [self.member, course_details.owner], - "subject": email_template.subject, - "header": email_template.subject, - "message": message - } - frappe.enqueue(method=frappe.sendmail, queue="short", timeout=300, is_async=True, **email_args) + elif self.status == "Withdrawn": + email_args = { + "recipients": [self.member, course_details.owner], + "subject": email_template.subject, + "header": email_template.subject, + "message": message, + } + frappe.enqueue( + method=frappe.sendmail, queue="short", timeout=300, is_async=True, **email_args + ) + + def get_email_template(self, template_name): + template = frappe.db.get_single_value("LMS Settings", template_name) + if template: + return frappe.get_doc("Email Template", template) - def get_email_template(self, template_name): - template = frappe.db.get_single_value('LMS Settings', template_name) - if template: - return frappe.get_doc('Email Template', template) @frappe.whitelist() def has_requested(course): - return frappe.db.count('LMS Mentor Request', - filters = { - 'member': frappe.session.user, - 'course': course, - 'status': ['in', ('Pending', 'Approved')] - } - ) + return frappe.db.count( + "LMS Mentor Request", + filters={ + "member": frappe.session.user, + "course": course, + "status": ["in", ("Pending", "Approved")], + }, + ) + @frappe.whitelist() def create_request(course): - if not has_requested(course): - request = frappe.get_doc({ - 'doctype': 'LMS Mentor Request', - 'member': frappe.session.user, - 'course': course, - 'status': 'Pending' - }) - request.save(ignore_permissions=True) - request.send_creation_email() - return 'OK' + if not has_requested(course): + request = frappe.get_doc( + { + "doctype": "LMS Mentor Request", + "member": frappe.session.user, + "course": course, + "status": "Pending", + } + ) + request.save(ignore_permissions=True) + request.send_creation_email() + return "OK" + + else: + return "Already Applied" - else: - return 'Already Applied' @frappe.whitelist() def cancel_request(course): - request = frappe.get_doc('LMS Mentor Request',{ - 'member': frappe.session.user, - 'course': course, - 'status': ['in', ('Pending', 'Approved')] - } - ) - request.status = 'Withdrawn' - request.save(ignore_permissions=True) - return 'OK' + request = frappe.get_doc( + "LMS Mentor Request", + { + "member": frappe.session.user, + "course": course, + "status": ["in", ("Pending", "Approved")], + }, + ) + request.status = "Withdrawn" + request.save(ignore_permissions=True) + return "OK" diff --git a/lms/lms/doctype/lms_mentor_request/test_lms_mentor_request.py b/lms/lms/doctype/lms_mentor_request/test_lms_mentor_request.py index 3441f72f..acf8a390 100644 --- a/lms/lms/doctype/lms_mentor_request/test_lms_mentor_request.py +++ b/lms/lms/doctype/lms_mentor_request/test_lms_mentor_request.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2021, FOSS United and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestLMSMentorRequest(unittest.TestCase): pass diff --git a/lms/lms/doctype/lms_option/lms_option.py b/lms/lms/doctype/lms_option/lms_option.py index d64d78b5..d09df23c 100644 --- a/lms/lms/doctype/lms_option/lms_option.py +++ b/lms/lms/doctype/lms_option/lms_option.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class LMSOption(Document): pass diff --git a/lms/lms/doctype/lms_quiz/lms_quiz.js b/lms/lms/doctype/lms_quiz/lms_quiz.js index 5f8fa5d3..949c8f0d 100644 --- a/lms/lms/doctype/lms_quiz/lms_quiz.js +++ b/lms/lms/doctype/lms_quiz/lms_quiz.js @@ -1,8 +1,7 @@ // Copyright (c) 2021, FOSS United and contributors // For license information, please see license.txt -frappe.ui.form.on('LMS Quiz', { - // refresh: function(frm) { - - // } +frappe.ui.form.on("LMS Quiz", { + // refresh: function(frm) { + // } }); diff --git a/lms/lms/doctype/lms_quiz/lms_quiz.py b/lms/lms/doctype/lms_quiz/lms_quiz.py index 8b5f79cb..6879c773 100644 --- a/lms/lms/doctype/lms_quiz/lms_quiz.py +++ b/lms/lms/doctype/lms_quiz/lms_quiz.py @@ -1,129 +1,137 @@ # Copyright (c) 2021, FOSS United and contributors # For license information, please see license.txt -import frappe -from frappe.model.document import Document import json + +import frappe from frappe import _ +from frappe.model.document import Document from frappe.utils import cstr + from lms.lms.utils import generate_slug + class LMSQuiz(Document): + def autoname(self): + if not self.name: + self.name = generate_slug(self.title, "LMS Quiz") - def autoname(self): - if not self.name: - self.name = generate_slug(self.title, "LMS Quiz") + def validate(self): + self.validate_correct_answers() + def validate_correct_answers(self): + for question in self.questions: + correct_options = self.get_correct_options(question) - def validate(self): - self.validate_correct_answers() + if len(correct_options) > 1: + question.multiple = 1 + if not len(correct_options): + frappe.throw( + _("At least one option must be correct for this question: {0}").format( + frappe.bold(question.question) + ) + ) - def validate_correct_answers(self): - for question in self.questions: - correct_options = self.get_correct_options(question) + def get_correct_options(self, question): + correct_option_fields = [ + "is_correct_1", + "is_correct_2", + "is_correct_3", + "is_correct_4", + ] + return list(filter(lambda x: question.get(x) == 1, correct_option_fields)) - if len(correct_options) > 1: - question.multiple = 1 + def get_last_submission_details(self): + """Returns the latest submission for this user.""" + user = frappe.session.user + if not user or user == "Guest": + return - if not len(correct_options): - frappe.throw(_("At least one option must be correct for this question: {0}").format(frappe.bold(question.question))) + result = frappe.get_all( + "LMS Quiz Submission", + fields="*", + filters={"owner": user, "quiz": self.name}, + order_by="creation desc", + page_length=1, + ) - - def get_correct_options(self, question): - correct_option_fields = ["is_correct_1", "is_correct_2", "is_correct_3", "is_correct_4"] - return list(filter(lambda x: question.get(x) == 1, correct_option_fields)) - - - def get_last_submission_details(self): - """Returns the latest submission for this user. - """ - user = frappe.session.user - if not user or user == "Guest": - return - - result = frappe.get_all('LMS Quiz Submission', - fields="*", - filters={ - "owner": user, - "quiz": self.name - }, - order_by="creation desc", - page_length=1) - - if result: - return result[0] + if result: + return result[0] @frappe.whitelist() def quiz_summary(quiz, results): - score = 0 - results = results and json.loads(results) + score = 0 + results = results and json.loads(results) - for result in results: - correct = result["is_correct"][0] - result["question"] = frappe.db.get_value("LMS Quiz Question", - {"parent": quiz, - "idx": result["question_index"]}, - ["question"]) + for result in results: + correct = result["is_correct"][0] + result["question"] = frappe.db.get_value( + "LMS Quiz Question", {"parent": quiz, "idx": result["question_index"]}, ["question"] + ) - for point in result["is_correct"]: - correct = correct and point + for point in result["is_correct"]: + correct = correct and point - result["result"] = "Right" if correct else "Wrong" - score += correct + result["result"] = "Right" if correct else "Wrong" + score += correct - del result["is_correct"] - del result["question_index"] + del result["is_correct"] + del result["question_index"] - frappe.get_doc({ - "doctype": "LMS Quiz Submission", - "quiz": quiz, - "result": results, - "score": score, - "member": frappe.session.user - }).save(ignore_permissions=True) + frappe.get_doc( + { + "doctype": "LMS Quiz Submission", + "quiz": quiz, + "result": results, + "score": score, + "member": frappe.session.user, + } + ).save(ignore_permissions=True) - return score + return score @frappe.whitelist() def save_quiz(quiz_title, questions, quiz): - if quiz: - doc = frappe.get_doc("LMS Quiz", quiz) - else: - doc = frappe.get_doc({ - "doctype": "LMS Quiz", - }) + if quiz: + doc = frappe.get_doc("LMS Quiz", quiz) + else: + doc = frappe.get_doc( + { + "doctype": "LMS Quiz", + } + ) - doc.update({ - "title": quiz_title - }) - doc.save(ignore_permissions=True) + doc.update({"title": quiz_title}) + doc.save(ignore_permissions=True) - for index, row in enumerate(json.loads(questions)): - if row["question_name"]: - question_doc = frappe.get_doc("LMS Quiz Question", row["question_name"]) - else: - question_doc = frappe.get_doc({ - "doctype": "LMS Quiz Question", - "parent": doc.name, - "parenttype": "LMS Quiz", - "parentfield": "questions", - "idx": index + 1 - }) + for index, row in enumerate(json.loads(questions)): + if row["question_name"]: + question_doc = frappe.get_doc("LMS Quiz Question", row["question_name"]) + else: + question_doc = frappe.get_doc( + { + "doctype": "LMS Quiz Question", + "parent": doc.name, + "parenttype": "LMS Quiz", + "parentfield": "questions", + "idx": index + 1, + } + ) - question_doc.update({ - "question": row["question"] - }) + question_doc.update({"question": row["question"]}) - for num in range(1,5): - question_doc.update({ - "option_" + cstr(num): row["option_" + cstr(num)], - "explanation_" + cstr(num): row["explanation_" + cstr(num)], - "is_correct_" + cstr(num): row["is_correct_" + cstr(num)] - }) + for num in range(1, 5): + question_doc.update( + { + "option_" + cstr(num): row["option_" + cstr(num)], + "explanation_" + cstr(num): row["explanation_" + cstr(num)], + "is_correct_" + cstr(num): row["is_correct_" + cstr(num)], + } + ) - question_doc.save(ignore_permissions=True) + question_doc.save(ignore_permissions=True) - return doc.name + return doc.name diff --git a/lms/lms/doctype/lms_quiz/test_lms_quiz.py b/lms/lms/doctype/lms_quiz/test_lms_quiz.py index ef004896..f51a8cd4 100644 --- a/lms/lms/doctype/lms_quiz/test_lms_quiz.py +++ b/lms/lms/doctype/lms_quiz/test_lms_quiz.py @@ -3,39 +3,45 @@ # import frappe import unittest + import frappe + class TestLMSQuiz(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + frappe.get_doc({"doctype": "LMS Quiz", "title": "Test Quiz"}).save( + ignore_permissions=True + ) - @classmethod - def setUpClass(cls) -> None: - frappe.get_doc({ - "doctype": "LMS Quiz", - "title": "Test Quiz" - }).save(ignore_permissions=True) + def test_with_multiple_options(self): + quiz = frappe.get_doc("LMS Quiz", "test-quiz") + quiz.append( + "questions", + { + "question": "Question multiple", + "option_1": "Option 1", + "is_correct_1": 1, + "option_2": "Option 2", + "is_correct_2": 1, + }, + ) + quiz.save() + self.assertTrue(quiz.questions[0].multiple) - def test_with_multiple_options(self): - quiz = frappe.get_doc("LMS Quiz", "test-quiz") - quiz.append("questions", { - "question": "Question multiple", - "option_1": "Option 1", - "is_correct_1": 1, - "option_2": "Option 2", - "is_correct_2": 1 - }) - quiz.save() - self.assertTrue(quiz.questions[0].multiple) + def test_with_no_correct_option(self): + quiz = frappe.get_doc("LMS Quiz", "test-quiz") + quiz.append( + "questions", + { + "question": "Question no correct option", + "option_1": "Option 1", + "option_2": "Option 2", + }, + ) + self.assertRaises(frappe.ValidationError, quiz.save) - def test_with_no_correct_option(self): - quiz = frappe.get_doc("LMS Quiz", "test-quiz") - quiz.append("questions", { - "question": "Question no correct option", - "option_1": "Option 1", - "option_2": "Option 2", - }) - self.assertRaises(frappe.ValidationError, quiz.save) - - @classmethod - def tearDownClass(cls) -> None: - frappe.db.delete("LMS Quiz", "test-quiz") - frappe.db.delete("LMS Quiz Question", {"parent": "test-quiz"}) + @classmethod + def tearDownClass(cls) -> None: + frappe.db.delete("LMS Quiz", "test-quiz") + frappe.db.delete("LMS Quiz Question", {"parent": "test-quiz"}) diff --git a/lms/lms/doctype/lms_quiz_question/lms_quiz_question.py b/lms/lms/doctype/lms_quiz_question/lms_quiz_question.py index 0435197b..bfb41a80 100644 --- a/lms/lms/doctype/lms_quiz_question/lms_quiz_question.py +++ b/lms/lms/doctype/lms_quiz_question/lms_quiz_question.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class LMSQuizQuestion(Document): pass diff --git a/lms/lms/doctype/lms_quiz_result/lms_quiz_result.py b/lms/lms/doctype/lms_quiz_result/lms_quiz_result.py index cfe9a9de..1a19de70 100644 --- a/lms/lms/doctype/lms_quiz_result/lms_quiz_result.py +++ b/lms/lms/doctype/lms_quiz_result/lms_quiz_result.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class LMSQuizResult(Document): pass diff --git a/lms/lms/doctype/lms_quiz_submission/lms_quiz_submission.js b/lms/lms/doctype/lms_quiz_submission/lms_quiz_submission.js index b4b0c176..8d1554ce 100644 --- a/lms/lms/doctype/lms_quiz_submission/lms_quiz_submission.js +++ b/lms/lms/doctype/lms_quiz_submission/lms_quiz_submission.js @@ -1,8 +1,7 @@ // Copyright (c) 2021, FOSS United and contributors // For license information, please see license.txt -frappe.ui.form.on('LMS Quiz Submission', { - // refresh: function(frm) { - - // } +frappe.ui.form.on("LMS Quiz Submission", { + // refresh: function(frm) { + // } }); diff --git a/lms/lms/doctype/lms_quiz_submission/lms_quiz_submission.py b/lms/lms/doctype/lms_quiz_submission/lms_quiz_submission.py index 3d1e29c7..d8eeba65 100644 --- a/lms/lms/doctype/lms_quiz_submission/lms_quiz_submission.py +++ b/lms/lms/doctype/lms_quiz_submission/lms_quiz_submission.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class LMSQuizSubmission(Document): pass diff --git a/lms/lms/doctype/lms_quiz_submission/test_lms_quiz_submission.py b/lms/lms/doctype/lms_quiz_submission/test_lms_quiz_submission.py index eddd8df4..eaabcd53 100644 --- a/lms/lms/doctype/lms_quiz_submission/test_lms_quiz_submission.py +++ b/lms/lms/doctype/lms_quiz_submission/test_lms_quiz_submission.py @@ -4,5 +4,6 @@ # import frappe import unittest + class TestLMSQuizSubmission(unittest.TestCase): pass diff --git a/lms/lms/doctype/lms_section/lms_section.py b/lms/lms/doctype/lms_section/lms_section.py index a40f86c5..99b3f975 100644 --- a/lms/lms/doctype/lms_section/lms_section.py +++ b/lms/lms/doctype/lms_section/lms_section.py @@ -1,37 +1,34 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2021, FOSS United and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document + class LMSSection(Document): - def __repr__(self): - return f"" + def __repr__(self): + return f"" - def get_exercise(self): - if self.type == "exercise": - return frappe.get_doc("Exercise", self.id) + def get_exercise(self): + if self.type == "exercise": + return frappe.get_doc("Exercise", self.id) - def get_quiz(self): - if self.type == "quiz": - return frappe.get_doc("LMS Quiz", self.id) + def get_quiz(self): + if self.type == "quiz": + return frappe.get_doc("LMS Quiz", self.id) - def get_latest_code_for_user(self): - """Returns the latest code for the logged in user. - """ - if not frappe.session.user or frappe.session.user == "Guest": - return self.contents - result = frappe.get_all('Code Revision', - fields=["code"], - filters={ - "author": frappe.session.user, - "section": self.name - }, - order_by="creation desc", - page_length=1) - if result: - return result[0]['code'] - else: - return self.contents + def get_latest_code_for_user(self): + """Returns the latest code for the logged in user.""" + if not frappe.session.user or frappe.session.user == "Guest": + return self.contents + result = frappe.get_all( + "Code Revision", + fields=["code"], + filters={"author": frappe.session.user, "section": self.name}, + order_by="creation desc", + page_length=1, + ) + if result: + return result[0]["code"] + else: + return self.contents diff --git a/lms/lms/doctype/lms_settings/lms_settings.js b/lms/lms/doctype/lms_settings/lms_settings.js index a4a0f2d3..e3620bc8 100644 --- a/lms/lms/doctype/lms_settings/lms_settings.js +++ b/lms/lms/doctype/lms_settings/lms_settings.js @@ -1,8 +1,7 @@ // Copyright (c) 2021, FOSS United and contributors // For license information, please see license.txt -frappe.ui.form.on('LMS Settings', { - // refresh: function(frm) { - - // } +frappe.ui.form.on("LMS Settings", { + // refresh: function(frm) { + // } }); diff --git a/lms/lms/doctype/lms_settings/lms_settings.py b/lms/lms/doctype/lms_settings/lms_settings.py index ab23693d..d8afde6e 100644 --- a/lms/lms/doctype/lms_settings/lms_settings.py +++ b/lms/lms/doctype/lms_settings/lms_settings.py @@ -1,12 +1,10 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2021, FOSS United and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe -from frappe.model.document import Document from frappe import _ +from frappe.model.document import Document + class LMSSettings(Document): - pass - + pass diff --git a/lms/lms/doctype/lms_settings/test_lms_settings.py b/lms/lms/doctype/lms_settings/test_lms_settings.py index 66b75c14..851c9dce 100644 --- a/lms/lms/doctype/lms_settings/test_lms_settings.py +++ b/lms/lms/doctype/lms_settings/test_lms_settings.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2021, FOSS United and Contributors # See license.txt -from __future__ import unicode_literals # import frappe import unittest + class TestLMSSettings(unittest.TestCase): pass diff --git a/lms/lms/doctype/preferred_function/preferred_function.py b/lms/lms/doctype/preferred_function/preferred_function.py index e66670a5..78884eba 100644 --- a/lms/lms/doctype/preferred_function/preferred_function.py +++ b/lms/lms/doctype/preferred_function/preferred_function.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class PreferredFunction(Document): pass diff --git a/lms/lms/doctype/preferred_industry/preferred_industry.js b/lms/lms/doctype/preferred_industry/preferred_industry.js index c5500d10..07980f63 100644 --- a/lms/lms/doctype/preferred_industry/preferred_industry.js +++ b/lms/lms/doctype/preferred_industry/preferred_industry.js @@ -1,8 +1,7 @@ // Copyright (c) 2021, Frappe and contributors // For license information, please see license.txt -frappe.ui.form.on('Preferred Industry', { - // refresh: function(frm) { - - // } +frappe.ui.form.on("Preferred Industry", { + // refresh: function(frm) { + // } }); diff --git a/lms/lms/doctype/preferred_industry/preferred_industry.py b/lms/lms/doctype/preferred_industry/preferred_industry.py index 35790487..4489baaf 100644 --- a/lms/lms/doctype/preferred_industry/preferred_industry.py +++ b/lms/lms/doctype/preferred_industry/preferred_industry.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class PreferredIndustry(Document): pass diff --git a/lms/lms/doctype/preferred_industry/test_preferred_industry.py b/lms/lms/doctype/preferred_industry/test_preferred_industry.py index 57a7c1a6..cd26452c 100644 --- a/lms/lms/doctype/preferred_industry/test_preferred_industry.py +++ b/lms/lms/doctype/preferred_industry/test_preferred_industry.py @@ -4,5 +4,6 @@ # import frappe import unittest + class TestPreferredIndustry(unittest.TestCase): pass diff --git a/lms/lms/doctype/related_courses/related_courses.js b/lms/lms/doctype/related_courses/related_courses.js index b68c26c2..9960d228 100644 --- a/lms/lms/doctype/related_courses/related_courses.js +++ b/lms/lms/doctype/related_courses/related_courses.js @@ -1,8 +1,7 @@ // Copyright (c) 2022, Frappe and contributors // For license information, please see license.txt -frappe.ui.form.on('Related Courses', { - // refresh: function(frm) { - - // } +frappe.ui.form.on("Related Courses", { + // refresh: function(frm) { + // } }); diff --git a/lms/lms/doctype/related_courses/related_courses.py b/lms/lms/doctype/related_courses/related_courses.py index 443ad08b..299849aa 100644 --- a/lms/lms/doctype/related_courses/related_courses.py +++ b/lms/lms/doctype/related_courses/related_courses.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class RelatedCourses(Document): pass diff --git a/lms/lms/doctype/related_courses/test_related_courses.py b/lms/lms/doctype/related_courses/test_related_courses.py index 6bac57b7..5c284edf 100644 --- a/lms/lms/doctype/related_courses/test_related_courses.py +++ b/lms/lms/doctype/related_courses/test_related_courses.py @@ -4,5 +4,6 @@ # import frappe import unittest + class TestRelatedCourses(unittest.TestCase): pass diff --git a/lms/lms/doctype/skill/skill.js b/lms/lms/doctype/skill/skill.js index b4cb4eb7..5a1994cb 100644 --- a/lms/lms/doctype/skill/skill.js +++ b/lms/lms/doctype/skill/skill.js @@ -1,8 +1,7 @@ // Copyright (c) 2021, Frappe and contributors // For license information, please see license.txt -frappe.ui.form.on('Skill', { - // refresh: function(frm) { - - // } +frappe.ui.form.on("Skill", { + // refresh: function(frm) { + // } }); diff --git a/lms/lms/doctype/skill/skill.py b/lms/lms/doctype/skill/skill.py index 4ff2164e..cad82055 100644 --- a/lms/lms/doctype/skill/skill.py +++ b/lms/lms/doctype/skill/skill.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class Skill(Document): pass diff --git a/lms/lms/doctype/skill/test_skill.py b/lms/lms/doctype/skill/test_skill.py index 0b3835eb..0e49e142 100644 --- a/lms/lms/doctype/skill/test_skill.py +++ b/lms/lms/doctype/skill/test_skill.py @@ -4,5 +4,6 @@ # import frappe import unittest + class TestSkill(unittest.TestCase): pass diff --git a/lms/lms/doctype/skills/skills.js b/lms/lms/doctype/skills/skills.js index 7545dd0c..05163bb9 100644 --- a/lms/lms/doctype/skills/skills.js +++ b/lms/lms/doctype/skills/skills.js @@ -1,8 +1,7 @@ // Copyright (c) 2021, Frappe and contributors // For license information, please see license.txt -frappe.ui.form.on('Skills', { - // refresh: function(frm) { - - // } +frappe.ui.form.on("Skills", { + // refresh: function(frm) { + // } }); diff --git a/lms/lms/doctype/skills/skills.py b/lms/lms/doctype/skills/skills.py index c6cbc49a..ce0b8c3e 100644 --- a/lms/lms/doctype/skills/skills.py +++ b/lms/lms/doctype/skills/skills.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class Skills(Document): pass diff --git a/lms/lms/doctype/skills/test_skills.py b/lms/lms/doctype/skills/test_skills.py index 9e17743e..231742f9 100644 --- a/lms/lms/doctype/skills/test_skills.py +++ b/lms/lms/doctype/skills/test_skills.py @@ -4,5 +4,6 @@ # import frappe import unittest + class TestSkills(unittest.TestCase): pass diff --git a/lms/lms/doctype/work_experience/work_experience.py b/lms/lms/doctype/work_experience/work_experience.py index 38ef7d00..f86ab7f2 100644 --- a/lms/lms/doctype/work_experience/work_experience.py +++ b/lms/lms/doctype/work_experience/work_experience.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class WorkExperience(Document): pass diff --git a/lms/lms/md.py b/lms/lms/md.py index b02ed68f..1a6b9ed7 100644 --- a/lms/lms/md.py +++ b/lms/lms/md.py @@ -13,102 +13,113 @@ that macro. The function will get the argument passed to the macro as argument. """ -import frappe +import html as HTML import re -from bs4 import BeautifulSoup +import xml.etree.ElementTree as etree + +import frappe import markdown +from bs4 import BeautifulSoup from markdown import Extension from markdown.inlinepatterns import InlineProcessor -import xml.etree.ElementTree as etree -import html as HTML + def markdown_to_html(text): - """Renders markdown text into html. - """ - return markdown.markdown(text, extensions=['fenced_code', MacroExtension()]) + """Renders markdown text into html.""" + return markdown.markdown(text, extensions=["fenced_code", MacroExtension()]) + def find_macros(text): - """Returns all macros in the given text. + """Returns all macros in the given text. + + >>> find_macros(text) + [ + ('YouTubeVideo': 'abcd1234') + ('Exercise', 'two-circles'), + ('Exercise', 'four-circles') + ] + """ + if not text: + return [] + macros = re.findall(MACRO_RE, text) + # remove the quotes around the argument + return [(name, _remove_quotes(arg)) for name, arg in macros] - >>> find_macros(text) - [ - ('YouTubeVideo': 'abcd1234') - ('Exercise', 'two-circles'), - ('Exercise', 'four-circles') - ] - """ - if not text: - return [] - macros = re.findall(MACRO_RE, text) - # remove the quotes around the argument - return [(name, _remove_quotes(arg)) for name, arg in macros] def _remove_quotes(value): - """Removes quotes around a value. + """Removes quotes around a value. - Also strips the whitespace. + Also strips the whitespace. - >>> _remove_quotes('"hello"') - 'hello' - >>> _remove_quotes("'hello'") - 'hello' - >>> _remove_quotes("hello") - 'hello' - """ - return value.strip(" '\"") + >>> _remove_quotes('"hello"') + 'hello' + >>> _remove_quotes("'hello'") + 'hello' + >>> _remove_quotes("hello") + 'hello' + """ + return value.strip(" '\"") def get_macro_registry(): - d = frappe.get_hooks("lms_markdown_macro_renderers") or {} - return {name: frappe.get_attr(klass[0]) for name, klass in d.items()} + d = frappe.get_hooks("lms_markdown_macro_renderers") or {} + return {name: frappe.get_attr(klass[0]) for name, klass in d.items()} + def render_macro(macro_name, macro_argument): - # stripping the quotes on either side of the argument - macro_argument = _remove_quotes(macro_argument) + # stripping the quotes on either side of the argument + macro_argument = _remove_quotes(macro_argument) - registry = get_macro_registry() - if macro_name in registry: - return registry[macro_name](macro_argument) - else: - return f"

Unknown macro: {macro_name}

" + registry = get_macro_registry() + if macro_name in registry: + return registry[macro_name](macro_argument) + else: + return f"

Unknown macro: {macro_name}

" + + +MACRO_RE = r"{{ *(\w+)\(([^{}]*)\) *}}" -MACRO_RE = r'{{ *(\w+)\(([^{}]*)\) *}}' class MacroExtension(Extension): - """MacroExtension is a markdown extension to support macro syntax. - """ - def extendMarkdown(self, md): - self.md = md - pattern = MacroInlineProcessor(MACRO_RE) - pattern.md = md - md.inlinePatterns.register(pattern, 'macro', 75) + """MacroExtension is a markdown extension to support macro syntax.""" + + def extendMarkdown(self, md): + self.md = md + pattern = MacroInlineProcessor(MACRO_RE) + pattern.md = md + md.inlinePatterns.register(pattern, "macro", 75) + class MacroInlineProcessor(InlineProcessor): - """MacroInlineProcessor is class that is handles the logic - of how to render each macro occurence in the markdown text. - """ - def handleMatch(self, m, data): - """Handles each macro match and return rendered contents - for that macro as an etree node. - """ - macro = m.group(1) - arg = m.group(2) - html = render_macro(macro, arg) - html = sanitize_html(str(html), macro) - e = etree.fromstring(html) - return e, m.start(0), m.end(0) + """MacroInlineProcessor is class that is handles the logic + of how to render each macro occurence in the markdown text. + """ + + def handleMatch(self, m, data): + """Handles each macro match and return rendered contents + for that macro as an etree node. + """ + macro = m.group(1) + arg = m.group(2) + html = render_macro(macro, arg) + html = sanitize_html(str(html), macro) + e = etree.fromstring(html) + return e, m.start(0), m.end(0) + def sanitize_html(html, macro): - """Sanitize the html using BeautifulSoup. + """Sanitize the html using BeautifulSoup. - The markdown processor request the correct markup and crashes on - any broken tags. This makes sures that all those things are fixed - before passing to the etree parser. - """ - soup = BeautifulSoup(html, features="lxml") - nodes = soup.body.children - classname = "" - if macro == "YouTubeVideo": - classname = "lesson-video" + The markdown processor request the correct markup and crashes on + any broken tags. This makes sures that all those things are fixed + before passing to the etree parser. + """ + soup = BeautifulSoup(html, features="lxml") + nodes = soup.body.children + classname = "" + if macro == "YouTubeVideo": + classname = "lesson-video" - return "
" + "\n".join(str(node) for node in nodes) + "
" + return ( + "
" + "\n".join(str(node) for node in nodes) + "
" + ) diff --git a/lms/lms/models.py b/lms/lms/models.py index 83d0c673..a0b2d629 100644 --- a/lms/lms/models.py +++ b/lms/lms/models.py @@ -1,4 +1,5 @@ """Handy module to make access to all doctypes from a single place. """ +from .doctype.lms_batch_membership.lms_batch_membership import \ + LMSBatchMembership as Membership from .doctype.lms_course.lms_course import LMSCourse as Course -from .doctype.lms_batch_membership.lms_batch_membership import LMSBatchMembership as Membership diff --git a/lms/lms/notification/certificate_request_creation/certificate_request_creation.py b/lms/lms/notification/certificate_request_creation/certificate_request_creation.py index e1ada619..80b7b873 100644 --- a/lms/lms/notification/certificate_request_creation/certificate_request_creation.py +++ b/lms/lms/notification/certificate_request_creation/certificate_request_creation.py @@ -1,5 +1,6 @@ import frappe + def get_context(context): # do your magic here pass diff --git a/lms/lms/notification/certificate_request_reminder/certificate_request_reminder.py b/lms/lms/notification/certificate_request_reminder/certificate_request_reminder.py index e1ada619..80b7b873 100644 --- a/lms/lms/notification/certificate_request_reminder/certificate_request_reminder.py +++ b/lms/lms/notification/certificate_request_reminder/certificate_request_reminder.py @@ -1,5 +1,6 @@ import frappe + def get_context(context): # do your magic here pass diff --git a/lms/lms/report/course_progress_summary/course_progress_summary.js b/lms/lms/report/course_progress_summary/course_progress_summary.js index 3d1150a8..670669d1 100644 --- a/lms/lms/report/course_progress_summary/course_progress_summary.js +++ b/lms/lms/report/course_progress_summary/course_progress_summary.js @@ -3,13 +3,13 @@ /* eslint-disable */ frappe.query_reports["Course Progress Summary"] = { - "filters": [ - { - "fieldname": "course", - "label": __("Course"), - "fieldtype": "Link", - "options": "LMS Course", - "reqd": 1, - } - ] + filters: [ + { + fieldname: "course", + label: __("Course"), + fieldtype: "Link", + options: "LMS Course", + reqd: 1, + }, + ], }; diff --git a/lms/lms/report/course_progress_summary/course_progress_summary.py b/lms/lms/report/course_progress_summary/course_progress_summary.py index a54249e4..9a54738e 100644 --- a/lms/lms/report/course_progress_summary/course_progress_summary.py +++ b/lms/lms/report/course_progress_summary/course_progress_summary.py @@ -2,110 +2,123 @@ # License: MIT. See LICENSE import frappe -from frappe.utils import cint from frappe import _ +from frappe.utils import cint + def execute(filters=None): - columns, data = [], [] - columns = get_columns() - data = get_data(filters) - charts = get_charts(data) - return columns, data, [], charts + columns, data = [], [] + columns = get_columns() + data = get_data(filters) + charts = get_charts(data) + return columns, data, [], charts + def get_data(filters=None): - summary = [] - query_filter = {} - if filters: - query_filter = { - "course": filters.course - } + summary = [] + query_filter = {} + if filters: + query_filter = {"course": filters.course} - memberships = frappe.get_all( - "LMS Batch Membership", - query_filter, - ["name", "course", "member", "member_name", "progress"], - order_by="course") + memberships = frappe.get_all( + "LMS Batch Membership", + query_filter, + ["name", "course", "member", "member_name", "progress"], + order_by="course", + ) - for membership in memberships: - summary.append(frappe._dict({ - "course": membership.name, - "course_name": frappe.db.get_value("LMS Course", membership.course, "title"), - "member": membership.member, - "member_name": membership.member_name, - "progress": cint(membership.progress) - })) + for membership in memberships: + summary.append( + frappe._dict( + { + "course": membership.name, + "course_name": frappe.db.get_value("LMS Course", membership.course, "title"), + "member": membership.member, + "member_name": membership.member_name, + "progress": cint(membership.progress), + } + ) + ) + + return summary - return summary def get_columns(): - return [ - { - "fieldname": "course", - "fieldtype": "Link", - "label": _("Course"), - "options": "LMS Course", - "width": 200 - }, - { - "fieldname": "course_name", - "fieldtype": "Data", - "label": _("Course Name"), - "width": 300 - }, - { - "fieldname": "member", - "fieldtype": "Link", - "label": _("Member"), - "options": "User", - "width": 200 - }, - { - "fieldname": "member_name", - "fieldtype": "Data", - "label": _("Member Name"), - "width": 150 - }, - { - "fieldname": "progress", - "fieldtype": "Data", - "label": _("Progress (%)"), - "width": 120 - } - ] + return [ + { + "fieldname": "course", + "fieldtype": "Link", + "label": _("Course"), + "options": "LMS Course", + "width": 200, + }, + { + "fieldname": "course_name", + "fieldtype": "Data", + "label": _("Course Name"), + "width": 300, + }, + { + "fieldname": "member", + "fieldtype": "Link", + "label": _("Member"), + "options": "User", + "width": 200, + }, + { + "fieldname": "member_name", + "fieldtype": "Data", + "label": _("Member Name"), + "width": 150, + }, + { + "fieldname": "progress", + "fieldtype": "Data", + "label": _("Progress (%)"), + "width": 120, + }, + ] + def get_charts(data): - if not data: - return None + if not data: + return None - completed = 0 - less_than_hundred = 0 - less_than_seventy = 0 - less_than_forty = 0 - less_than_ten = 0 + completed = 0 + less_than_hundred = 0 + less_than_seventy = 0 + less_than_forty = 0 + less_than_ten = 0 - for row in data: - if row.progress == 100: - completed += 1 - elif row.progress < 100 and row.progress > 70: - less_than_hundred += 1 - elif row.progress < 70 and row.progress > 40: - less_than_seventy += 1 - elif row.progress < 40 and row.progress > 10: - less_than_forty += 1 - elif row.progress < 10: - less_than_ten += 1 + for row in data: + if row.progress == 100: + completed += 1 + elif row.progress < 100 and row.progress > 70: + less_than_hundred += 1 + elif row.progress < 70 and row.progress > 40: + less_than_seventy += 1 + elif row.progress < 40 and row.progress > 10: + less_than_forty += 1 + elif row.progress < 10: + less_than_ten += 1 - charts = { - "data": { - "labels": ["0-10", "10-40", "40-70", "70-99", "100"], - "datasets": [ - { - "name": "Progress (%)", - "values": [less_than_ten, less_than_forty, less_than_seventy, less_than_hundred, completed] - } - ] - }, - "type": "pie", - "colors": ["#ff0e0e", "#ff9966", "#ffcc00", "#99cc33", "#339900"] - } - return charts + charts = { + "data": { + "labels": ["0-10", "10-40", "40-70", "70-99", "100"], + "datasets": [ + { + "name": "Progress (%)", + "values": [ + less_than_ten, + less_than_forty, + less_than_seventy, + less_than_hundred, + completed, + ], + } + ], + }, + "type": "pie", + "colors": ["#ff0e0e", "#ff9966", "#ffcc00", "#99cc33", "#339900"], + } + return charts diff --git a/lms/lms/test_utils.py b/lms/lms/test_utils.py index c3c582b3..3831c5a5 100644 --- a/lms/lms/test_utils.py +++ b/lms/lms/test_utils.py @@ -1,67 +1,75 @@ import unittest + import frappe -from .utils import slugify, get_evaluation_details -from lms.lms.doctype.lms_course.test_lms_course import new_course, new_user from frappe.utils import getdate +from lms.lms.doctype.lms_course.test_lms_course import new_course, new_user + +from .utils import get_evaluation_details, slugify + + class TestUtils(unittest.TestCase): - def test_simple(self): - self.assertEquals(slugify("hello-world"), "hello-world") - self.assertEquals(slugify("Hello World"), "hello-world") - self.assertEquals(slugify("Hello, World!"), "hello-world") + def test_simple(self): + self.assertEqual(slugify("hello-world"), "hello-world") + self.assertEqual(slugify("Hello World"), "hello-world") + self.assertEqual(slugify("Hello, World!"), "hello-world") - def test_duplicates(self): - self.assertEquals( - slugify("Hello World", ['hello-world']), - "hello-world-2") + def test_duplicates(self): + self.assertEqual(slugify("Hello World", ["hello-world"]), "hello-world-2") - self.assertEquals( - slugify("Hello World", ['hello-world', 'hello-world-2']), - "hello-world-3") + self.assertEqual( + slugify("Hello World", ["hello-world", "hello-world-2"]), "hello-world-3" + ) - def test_evaluation_details(self): - course = new_course("Test Evaluation Details", { - "enable_certification": 1, - "grant_certificate_after": "Evaluation", - "evaluator": "evaluator@example.com", - "max_attempts": 3, - "duration": 2 - }) - user = new_user("Eval", "eval@test.com") + def test_evaluation_details(self): + course = new_course( + "Test Evaluation Details", + { + "enable_certification": 1, + "grant_certificate_after": "Evaluation", + "evaluator": "evaluator@example.com", + "max_attempts": 3, + "duration": 2, + }, + ) + user = new_user("Eval", "eval@test.com") - # Two evaluations failed within max attempts. Check eligibility for a third evaluation - create_evaluation(user.name, course.name, getdate("21-03-2022"), 0.4, "Fail") - create_evaluation(user.name, course.name, getdate("12-04-2022"), 0.4, "Fail") - details = get_evaluation_details(course.name, user.name) - self.assertTrue(details.eligible) + # Two evaluations failed within max attempts. Check eligibility for a third evaluation + create_evaluation(user.name, course.name, getdate("21-03-2022"), 0.4, "Fail") + create_evaluation(user.name, course.name, getdate("12-04-2022"), 0.4, "Fail") + details = get_evaluation_details(course.name, user.name) + self.assertTrue(details.eligible) - # Three evaluations failed within max attempts. Check eligibility for a forth evaluation - create_evaluation(user.name, course.name, getdate("21-03-2022"), 0.4, "Fail") - create_evaluation(user.name, course.name, getdate("12-04-2022"), 0.4, "Fail") - create_evaluation(user.name, course.name, getdate("16-04-2022"), 0.4, "Fail") - details = get_evaluation_details(course.name, user.name) - self.assertFalse(details.eligible) + # Three evaluations failed within max attempts. Check eligibility for a forth evaluation + create_evaluation(user.name, course.name, getdate("21-03-2022"), 0.4, "Fail") + create_evaluation(user.name, course.name, getdate("12-04-2022"), 0.4, "Fail") + create_evaluation(user.name, course.name, getdate("16-04-2022"), 0.4, "Fail") + details = get_evaluation_details(course.name, user.name) + self.assertFalse(details.eligible) - # Three evaluations failed within max attempts. Check eligibility for a forth evaluation. Different Dates - create_evaluation(user.name, course.name, getdate("01-03-2022"), 0.4, "Fail") - create_evaluation(user.name, course.name, getdate("12-04-2022"), 0.4, "Fail") - create_evaluation(user.name, course.name, getdate("16-04-2022"), 0.4, "Fail") - details = get_evaluation_details(course.name, user.name) - self.assertFalse(details.eligible) + # Three evaluations failed within max attempts. Check eligibility for a forth evaluation. Different Dates + create_evaluation(user.name, course.name, getdate("01-03-2022"), 0.4, "Fail") + create_evaluation(user.name, course.name, getdate("12-04-2022"), 0.4, "Fail") + create_evaluation(user.name, course.name, getdate("16-04-2022"), 0.4, "Fail") + details = get_evaluation_details(course.name, user.name) + self.assertFalse(details.eligible) + + frappe.db.delete("LMS Certificate Evaluation", {"course": course.name}) + frappe.db.delete("LMS Course", course.name) + frappe.db.delete("User", user.name) - frappe.db.delete("LMS Certificate Evaluation", {"course": course.name}) - frappe.db.delete("LMS Course", course.name) - frappe.db.delete("User", user.name) def create_evaluation(user, course, date, rating, status): - evaluation = frappe.get_doc({ - "doctype": "LMS Certificate Evaluation", - "member": user, - "course": course, - "date": date, - "start_time": "12:00:00", - "end_time": "13:00:00", - "rating": rating, - "status": status - }) - evaluation.save(ignore_permissions=True) + evaluation = frappe.get_doc( + { + "doctype": "LMS Certificate Evaluation", + "member": user, + "course": course, + "date": date, + "start_time": "12:00:00", + "end_time": "13:00:00", + "rating": rating, + "status": status, + } + ) + evaluation.save(ignore_permissions=True) diff --git a/lms/lms/utils.py b/lms/lms/utils.py index 054d188b..6d657241 100644 --- a/lms/lms/utils.py +++ b/lms/lms/utils.py @@ -1,605 +1,646 @@ import re -import frappe -from frappe.utils import flt, cint, cstr, getdate, add_months, fmt_money, get_datetime, format_date -from lms.lms.md import markdown_to_html, find_macros import string + +import frappe from frappe import _ -from frappe.desk.doctype.notification_log.notification_log import make_notification_logs from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_result +from frappe.desk.doctype.notification_log.notification_log import \ + make_notification_logs +from frappe.utils import (add_months, cint, cstr, flt, fmt_money, format_date, + get_datetime, getdate) from frappe.utils.dateutils import get_period +from lms.lms.md import find_macros, markdown_to_html RE_SLUG_NOTALLOWED = re.compile("[^a-z0-9]+") def slugify(title, used_slugs=[]): - """Converts title to a slug. + """Converts title to a slug. - If a list of used slugs is specified, it will make sure the generated slug - is not one of them. + If a list of used slugs is specified, it will make sure the generated slug + is not one of them. - >>> slugify("Hello World!") - 'hello-world' - >>> slugify("Hello World!", ['hello-world']) - 'hello-world-2' - >>> slugify("Hello World!", ['hello-world', 'hello-world-2']) - 'hello-world-3' - """ - slug = RE_SLUG_NOTALLOWED.sub('-', title.lower()).strip('-') - used_slugs = set(used_slugs) + >>> slugify("Hello World!") + 'hello-world' + >>> slugify("Hello World!", ['hello-world']) + 'hello-world-2' + >>> slugify("Hello World!", ['hello-world', 'hello-world-2']) + 'hello-world-3' + """ + slug = RE_SLUG_NOTALLOWED.sub("-", title.lower()).strip("-") + used_slugs = set(used_slugs) - if slug not in used_slugs: - return slug + if slug not in used_slugs: + return slug - count = 2 - while True: - new_slug = f"{slug}-{count}" - if new_slug not in used_slugs: - return new_slug - count = count+1 + count = 2 + while True: + new_slug = f"{slug}-{count}" + if new_slug not in used_slugs: + return new_slug + count = count + 1 def generate_slug(title, doctype): - result = frappe.get_all( - doctype, - fields=['name']) - slugs = set([row['name'] for row in result]) - return slugify(title, used_slugs=slugs) + result = frappe.get_all(doctype, fields=["name"]) + slugs = {row["name"] for row in result} + return slugify(title, used_slugs=slugs) def get_membership(course, member, batch=None): - filters = { - "member": member, - "course": course - } - if batch: - filters["batch"] = batch + filters = {"member": member, "course": course} + if batch: + filters["batch"] = batch - membership = frappe.db.get_value("LMS Batch Membership", - filters, - ["name", "batch", "current_lesson", "member_type", "progress"], - as_dict=True) + membership = frappe.db.get_value( + "LMS Batch Membership", + filters, + ["name", "batch", "current_lesson", "member_type", "progress"], + as_dict=True, + ) - if membership and membership.batch: - membership.batch_title = frappe.db.get_value("LMS Batch", membership.batch, "title") - return membership + if membership and membership.batch: + membership.batch_title = frappe.db.get_value("LMS Batch", membership.batch, "title") + return membership def get_chapters(course): - """Returns all chapters of this course. - """ - if not course: - return [] - chapters = frappe.get_all("Chapter Reference", {"parent": course}, - ["idx", "chapter"], order_by="idx") - for chapter in chapters: - chapter_details = frappe.db.get_value("Course Chapter", { "name": chapter.chapter }, - ["name", "title", "description"], as_dict=True) - chapter.update(chapter_details) - return chapters + """Returns all chapters of this course.""" + if not course: + return [] + chapters = frappe.get_all( + "Chapter Reference", {"parent": course}, ["idx", "chapter"], order_by="idx" + ) + for chapter in chapters: + chapter_details = frappe.db.get_value( + "Course Chapter", + {"name": chapter.chapter}, + ["name", "title", "description"], + as_dict=True, + ) + chapter.update(chapter_details) + return chapters + def get_lessons(course, chapter=None): - """ If chapter is passed, returns lessons of only that chapter. - Else returns lessons of all chapters of the course """ - lessons = [] - if chapter: - return get_lesson_details(chapter) + """If chapter is passed, returns lessons of only that chapter. + Else returns lessons of all chapters of the course""" + lessons = [] + if chapter: + return get_lesson_details(chapter) - for chapter in get_chapters(course): - lesson = get_lesson_details(chapter) - lessons += lesson + for chapter in get_chapters(course): + lesson = get_lesson_details(chapter) + lessons += lesson - return lessons + return lessons def get_lesson_details(chapter): - lessons = [] - lesson_list = frappe.get_all("Lesson Reference", - {"parent": chapter.name}, - ["lesson", "idx"], - order_by="idx") + lessons = [] + lesson_list = frappe.get_all( + "Lesson Reference", {"parent": chapter.name}, ["lesson", "idx"], order_by="idx" + ) - for row in lesson_list: - lesson_details = frappe.db.get_value("Course Lesson", - row.lesson, - ["name", "title", "include_in_preview", "body", "creation", "youtube", "quiz_id", "question", "file_type"], - as_dict=True) - lesson_details.number = flt("{}.{}".format(chapter.idx, row.idx)) - lesson_details.icon = "icon-list" - macros = find_macros(lesson_details.body) + for row in lesson_list: + lesson_details = frappe.db.get_value( + "Course Lesson", + row.lesson, + [ + "name", + "title", + "include_in_preview", + "body", + "creation", + "youtube", + "quiz_id", + "question", + "file_type", + ], + as_dict=True, + ) + lesson_details.number = flt(f"{chapter.idx}.{row.idx}") + lesson_details.icon = "icon-list" + macros = find_macros(lesson_details.body) - for macro in macros: - if macro[0] == "YouTubeVideo": - lesson_details.icon = "icon-video" - elif macro[0] == "Quiz": - lesson_details.icon = "icon-quiz" - lessons.append(lesson_details) - return lessons + for macro in macros: + if macro[0] == "YouTubeVideo": + lesson_details.icon = "icon-video" + elif macro[0] == "Quiz": + lesson_details.icon = "icon-quiz" + lessons.append(lesson_details) + return lessons def get_tags(course): - tags = frappe.db.get_value("LMS Course", course, "tags") - return tags.split(",") if tags else [] + tags = frappe.db.get_value("LMS Course", course, "tags") + return tags.split(",") if tags else [] def get_instructors(course): - instructor_details = [] - instructors = frappe.get_all("Course Instructor", {"parent": course}, - ["instructor"], order_by="idx") - if not instructors: - instructors = frappe.db.get_value("LMS Course", course, "owner").split(" ") - for instructor in instructors: - instructor_details.append(frappe.db.get_value("User", - instructor.instructor, - ["name", "username", "full_name", "user_image"], - as_dict=True)) - return instructor_details + instructor_details = [] + instructors = frappe.get_all( + "Course Instructor", {"parent": course}, ["instructor"], order_by="idx" + ) + if not instructors: + instructors = frappe.db.get_value("LMS Course", course, "owner").split(" ") + for instructor in instructors: + instructor_details.append( + frappe.db.get_value( + "User", + instructor.instructor, + ["name", "username", "full_name", "user_image"], + as_dict=True, + ) + ) + return instructor_details def get_students(course, batch=None): - """Returns (email, full_name, username) of all the students of this batch as a list of dict. - """ - filters = { - "course": course, - "member_type": "Student" - } + """Returns (email, full_name, username) of all the students of this batch as a list of dict.""" + filters = {"course": course, "member_type": "Student"} - if batch: - filters["batch"] = batch + if batch: + filters["batch"] = batch - return frappe.get_all( - "LMS Batch Membership", - filters, - ["member"]) + return frappe.get_all("LMS Batch Membership", filters, ["member"]) def get_average_rating(course): - ratings = [review.rating for review in get_reviews(course)] - if not len(ratings): - return None - return sum(ratings)/len(ratings) + ratings = [review.rating for review in get_reviews(course)] + if not len(ratings): + return None + return sum(ratings) / len(ratings) def get_reviews(course): - reviews = frappe.get_all("LMS Course Review", - { - "course": course - }, - ["review", "rating", "owner", "creation"], - order_by= "creation desc") - out_of_ratings = frappe.db.get_all("DocField", - { - "parent": "LMS Course Review", - "fieldtype": "Rating" - }, - ["options"]) - out_of_ratings = (len(out_of_ratings) and out_of_ratings[0].options) or 5 - for review in reviews: - review.rating = review.rating * out_of_ratings - review.owner_details = frappe.db.get_value("User", - review.owner, ["name", "username", "full_name", "user_image"], as_dict=True) + reviews = frappe.get_all( + "LMS Course Review", + {"course": course}, + ["review", "rating", "owner", "creation"], + order_by="creation desc", + ) + out_of_ratings = frappe.db.get_all( + "DocField", {"parent": "LMS Course Review", "fieldtype": "Rating"}, ["options"] + ) + out_of_ratings = (len(out_of_ratings) and out_of_ratings[0].options) or 5 + for review in reviews: + review.rating = review.rating * out_of_ratings + review.owner_details = frappe.db.get_value( + "User", review.owner, ["name", "username", "full_name", "user_image"], as_dict=True + ) - return reviews + return reviews def get_sorted_reviews(course): - rating_count = rating_percent = frappe._dict() - keys = ["5.0", "4.0", "3.0", "2.0", "1.0"] - for key in keys: - rating_count[key] = 0 + rating_count = rating_percent = frappe._dict() + keys = ["5.0", "4.0", "3.0", "2.0", "1.0"] + for key in keys: + rating_count[key] = 0 - reviews = get_reviews(course) - for review in reviews: - rating_count[cstr(review.rating)] += 1 + reviews = get_reviews(course) + for review in reviews: + rating_count[cstr(review.rating)] += 1 - for key in keys: - rating_percent[key] = (rating_count[key]/len(reviews) * 100) + for key in keys: + rating_percent[key] = rating_count[key] / len(reviews) * 100 - - return rating_percent + return rating_percent def is_certified(course): - certificate = frappe.get_all("LMS Certificate", - { - "member": frappe.session.user, - "course": course - }) - if len(certificate): - return certificate[0].name - return + certificate = frappe.get_all( + "LMS Certificate", {"member": frappe.session.user, "course": course} + ) + if len(certificate): + return certificate[0].name + return def get_lesson_index(lesson_name): - """Returns the {chapter_index}.{lesson_index} for the lesson. - """ - lesson = frappe.db.get_value("Lesson Reference", - {"lesson": lesson_name}, ["idx", "parent"], as_dict=True) - if not lesson: - return "1.1" + """Returns the {chapter_index}.{lesson_index} for the lesson.""" + lesson = frappe.db.get_value( + "Lesson Reference", {"lesson": lesson_name}, ["idx", "parent"], as_dict=True + ) + if not lesson: + return "1.1" - chapter = frappe.db.get_value("Chapter Reference", - {"chapter": lesson.parent}, ["idx"], as_dict=True) - if not chapter: - return "1.1" + chapter = frappe.db.get_value( + "Chapter Reference", {"chapter": lesson.parent}, ["idx"], as_dict=True + ) + if not chapter: + return "1.1" - return f"{chapter.idx}.{lesson.idx}" + return f"{chapter.idx}.{lesson.idx}" def get_lesson_url(course, lesson_number): - if not lesson_number: - return - return f"/courses/{course}/learn/{lesson_number}" + if not lesson_number: + return + return f"/courses/{course}/learn/{lesson_number}" + def get_batch(course, batch_name): - return frappe.get_all("LMS Batch", {"name": batch_name, "course": course}) + return frappe.get_all("LMS Batch", {"name": batch_name, "course": course}) + def get_slugified_chapter_title(chapter): - return slugify(chapter) + return slugify(chapter) + def get_progress(course, lesson): - return frappe.db.get_value("LMS Course Progress", { - "course": course, - "owner": frappe.session.user, - "lesson": lesson - }, - ["status"]) + return frappe.db.get_value( + "LMS Course Progress", + {"course": course, "owner": frappe.session.user, "lesson": lesson}, + ["status"], + ) def render_html(lesson): - youtube = lesson.youtube - quiz_id = lesson.quiz_id - body = lesson.body + youtube = lesson.youtube + quiz_id = lesson.quiz_id + body = lesson.body - if youtube and "/" in youtube: - youtube = youtube.split("/")[-1] + if youtube and "/" in youtube: + youtube = youtube.split("/")[-1] - quiz_id = "{{ Quiz('" + quiz_id + "') }}" if quiz_id else "" - youtube = "{{ YouTubeVideo('" + youtube + "') }}" if youtube else "" - text = youtube + body + quiz_id + quiz_id = "{{ Quiz('" + quiz_id + "') }}" if quiz_id else "" + youtube = "{{ YouTubeVideo('" + youtube + "') }}" if youtube else "" + text = youtube + body + quiz_id - if lesson.question: - assignment = "{{ Assignment('" + lesson.question + "-" + lesson.file_type + "') }}" - text = text + assignment + if lesson.question: + assignment = "{{ Assignment('" + lesson.question + "-" + lesson.file_type + "') }}" + text = text + assignment - return markdown_to_html(text) + return markdown_to_html(text) def is_mentor(course, email): - """Checks if given user is a mentor for this course. - """ - if not email: - return False - return frappe.db.count("LMS Course Mentor Mapping", - { - "course": course, - "mentor": email - }) + """Checks if given user is a mentor for this course.""" + if not email: + return False + return frappe.db.count( + "LMS Course Mentor Mapping", {"course": course, "mentor": email} + ) def is_cohort_staff(course, user_email): - """Returns True if the user is either a mentor or a staff for one or more active cohorts of this course. - """ - staff = { - "doctype": "Cohort Staff", - "course": course, - "email": user_email - } - mentor = { - "doctype": "Cohort Mentor", - "course": course, - "email": user_email - } - return frappe.db.exists(staff) or frappe.db.exists(mentor) + """Returns True if the user is either a mentor or a staff for one or more active cohorts of this course.""" + staff = {"doctype": "Cohort Staff", "course": course, "email": user_email} + mentor = {"doctype": "Cohort Mentor", "course": course, "email": user_email} + return frappe.db.exists(staff) or frappe.db.exists(mentor) def get_mentors(course): - """Returns the list of all mentors for this course. - """ - course_mentors = [] - mentors = frappe.get_all("LMS Course Mentor Mapping", {"course": course}, ["mentor"]) - for mentor in mentors: - member = frappe.db.get_value("User", mentor.mentor, - ["name", "username", "full_name", "user_image"]) - member.batch_count = frappe.db.count("LMS Batch Membership", - { - "member": member.name, - "member_type": "Mentor" - }) - course_mentors.append(member) - return course_mentors + """Returns the list of all mentors for this course.""" + course_mentors = [] + mentors = frappe.get_all("LMS Course Mentor Mapping", {"course": course}, ["mentor"]) + for mentor in mentors: + member = frappe.db.get_value( + "User", mentor.mentor, ["name", "username", "full_name", "user_image"] + ) + member.batch_count = frappe.db.count( + "LMS Batch Membership", {"member": member.name, "member_type": "Mentor"} + ) + course_mentors.append(member) + return course_mentors def is_eligible_to_review(course, membership): - """ Checks if user is eligible to review the course """ - if not membership: - return False - if frappe.db.count("LMS Course Review", - { - "course": course, - "owner": frappe.session.user - }): - return False - return True + """Checks if user is eligible to review the course""" + if not membership: + return False + if frappe.db.count( + "LMS Course Review", {"course": course, "owner": frappe.session.user} + ): + return False + return True def get_course_progress(course, member=None): - """ Returns the course progress of the session user """ - lesson_count = len(get_lessons(course)) - if not lesson_count: - return 0 - completed_lessons = frappe.db.count("LMS Course Progress", - { - "course": course, - "owner": member or frappe.session.user, - "status": "Complete" - }) - precision = cint(frappe.db.get_default("float_precision")) or 3 - return flt(((completed_lessons/lesson_count) * 100), precision) + """Returns the course progress of the session user""" + lesson_count = len(get_lessons(course)) + if not lesson_count: + return 0 + completed_lessons = frappe.db.count( + "LMS Course Progress", + {"course": course, "owner": member or frappe.session.user, "status": "Complete"}, + ) + precision = cint(frappe.db.get_default("float_precision")) or 3 + return flt(((completed_lessons / lesson_count) * 100), precision) def get_initial_members(course): - members = frappe.get_all("LMS Batch Membership", - { - "course": course - }, - ["member"], - limit=3) + members = frappe.get_all( + "LMS Batch Membership", {"course": course}, ["member"], limit=3 + ) - member_details = [] - for member in members: - member_details.append(frappe.db.get_value("User", - member.member, ["name", "username", "full_name", "user_image"], as_dict=True)) + member_details = [] + for member in members: + member_details.append( + frappe.db.get_value( + "User", member.member, ["name", "username", "full_name", "user_image"], as_dict=True + ) + ) - return member_details + return member_details def is_instructor(course): - return len(list(filter(lambda x: x.name == frappe.session.user, get_instructors(course)))) > 0 + return ( + len(list(filter(lambda x: x.name == frappe.session.user, get_instructors(course)))) + > 0 + ) def convert_number_to_character(number): - return string.ascii_uppercase[number] + return string.ascii_uppercase[number] def get_signup_optin_checks(): - mapper = frappe._dict({ - "terms_of_use": { - "page_name": "terms_page", - "title": _("Terms of Use") - }, - "privacy_policy": { - "page_name": "privacy_policy_page", - "title": _("Privacy Policy") - }, - "cookie_policy": { - "page_name": "cookie_policy_page", - "title": _("Cookie Policy") - } - }) - checks = ["terms_of_use", "privacy_policy", "cookie_policy"] - links = [] + mapper = frappe._dict( + { + "terms_of_use": {"page_name": "terms_page", "title": _("Terms of Use")}, + "privacy_policy": {"page_name": "privacy_policy_page", "title": _("Privacy Policy")}, + "cookie_policy": {"page_name": "cookie_policy_page", "title": _("Cookie Policy")}, + } + ) + checks = ["terms_of_use", "privacy_policy", "cookie_policy"] + links = [] - for check in checks: - if frappe.db.get_single_value("LMS Settings", check): - page = frappe.db.get_single_value("LMS Settings", mapper[check].get("page_name")) - route = frappe.db.get_value("Web Page", page, "route") - links.append("" + mapper[check].get("title") + "") + for check in checks: + if frappe.db.get_single_value("LMS Settings", check): + page = frappe.db.get_single_value("LMS Settings", mapper[check].get("page_name")) + route = frappe.db.get_value("Web Page", page, "route") + links.append("" + mapper[check].get("title") + "") - return (", ").join(links) + return (", ").join(links) def get_popular_courses(): - courses = frappe.get_all("LMS Course", {"published": 1, "upcoming": 0}) - course_membership = [] + courses = frappe.get_all("LMS Course", {"published": 1, "upcoming": 0}) + course_membership = [] - for course in courses: - course_membership.append({ - "course": course.name, - "members": cint(frappe.db.count("LMS Batch Membership", {"course": course.name})) - }) + for course in courses: + course_membership.append( + { + "course": course.name, + "members": cint(frappe.db.count("LMS Batch Membership", {"course": course.name})), + } + ) - course_membership = sorted(course_membership, key = lambda x: x.get("members"), reverse=True) - return course_membership[:3] + course_membership = sorted( + course_membership, key=lambda x: x.get("members"), reverse=True + ) + return course_membership[:3] def get_evaluation_details(course, member=None): - info = frappe.db.get_value("LMS Course", course, ["grant_certificate_after", "max_attempts", "duration"], as_dict=True) - request = frappe.db.get_value("LMS Certificate Request", { - "course": course, - "member": member or frappe.session.user, - "date": [">=", getdate()] - }, ["date", "start_time", "end_time"], - as_dict=True) + info = frappe.db.get_value( + "LMS Course", + course, + ["grant_certificate_after", "max_attempts", "duration"], + as_dict=True, + ) + request = frappe.db.get_value( + "LMS Certificate Request", + { + "course": course, + "member": member or frappe.session.user, + "date": [">=", getdate()], + }, + ["date", "start_time", "end_time"], + as_dict=True, + ) - no_of_attempts = frappe.db.count("LMS Certificate Evaluation", { - "course": course, - "member": member or frappe.session.user, - "status": ["!=", "Pass"], - "creation": [">=", add_months(getdate(), -abs(cint(info.duration)))] - }) + no_of_attempts = frappe.db.count( + "LMS Certificate Evaluation", + { + "course": course, + "member": member or frappe.session.user, + "status": ["!=", "Pass"], + "creation": [">=", add_months(getdate(), -abs(cint(info.duration)))], + }, + ) - return frappe._dict({ - "eligible": info.grant_certificate_after == "Evaluation" and not request and no_of_attempts < info.max_attempts, - "request": request, - "no_of_attempts": no_of_attempts - }) + return frappe._dict( + { + "eligible": info.grant_certificate_after == "Evaluation" + and not request + and no_of_attempts < info.max_attempts, + "request": request, + "no_of_attempts": no_of_attempts, + } + ) def format_amount(amount, currency): - amount_reduced = amount / 1000 - if amount_reduced < 1: - return amount - precision = 0 if amount % 1000 == 0 else 1 - return _("{0}k").format(fmt_money(amount_reduced, precision, currency)) + amount_reduced = amount / 1000 + if amount_reduced < 1: + return amount + precision = 0 if amount % 1000 == 0 else 1 + return _("{0}k").format(fmt_money(amount_reduced, precision, currency)) def format_number(number): - number_reduced = number / 1000 - if number_reduced < 1: - return number - return "{0}k".format(frappe.utils.flt(number_reduced, 1)) + number_reduced = number / 1000 + if number_reduced < 1: + return number + return f"{frappe.utils.flt(number_reduced, 1)}k" def first_lesson_exists(course): - first_chapter = frappe.db.get_value("Chapter Reference", {"parent": course, "idx": 1}, "name") - if not first_chapter: - return False + first_chapter = frappe.db.get_value( + "Chapter Reference", {"parent": course, "idx": 1}, "name" + ) + if not first_chapter: + return False - first_lesson = frappe.db.get_value("Lesson Reference", {"parent": first_chapter, "idx": 1}, "name") - if not first_lesson: - return False + first_lesson = frappe.db.get_value( + "Lesson Reference", {"parent": first_chapter, "idx": 1}, "name" + ) + if not first_lesson: + return False - return True + return True def redirect_to_courses_list(): - frappe.local.flags.redirect_location = "/courses" - raise frappe.Redirect + frappe.local.flags.redirect_location = "/courses" + raise frappe.Redirect def has_course_instructor_role(member=None): - return frappe.db.get_value("Has Role", { - "parent": member or frappe.session.user, - "role": "Course Instructor" - }, "name") + return frappe.db.get_value( + "Has Role", + {"parent": member or frappe.session.user, "role": "Course Instructor"}, + "name", + ) def can_create_courses(member=None): - if not member: - member = frappe.session.user + if not member: + member = frappe.session.user - portal_course_creation = frappe.db.get_single_value("LMS Settings", "portal_course_creation") - return frappe.session.user != "Guest" and (portal_course_creation == "Anyone" or has_course_instructor_role(member)) + portal_course_creation = frappe.db.get_single_value( + "LMS Settings", "portal_course_creation" + ) + return frappe.session.user != "Guest" and ( + portal_course_creation == "Anyone" or has_course_instructor_role(member) + ) def has_course_moderator_role(member=None): - return frappe.db.get_value("Has Role", { - "parent": member or frappe.session.user, - "role": "Course Moderator" - }, "name") + return frappe.db.get_value( + "Has Role", + {"parent": member or frappe.session.user, "role": "Course Moderator"}, + "name", + ) def get_courses_under_review(): - return frappe.get_all("LMS Course", { - "status": "Under Review" - }, ["name", "upcoming", "title", "image", "enable_certification", "status", "published"] -) + return frappe.get_all( + "LMS Course", + {"status": "Under Review"}, + ["name", "upcoming", "title", "image", "enable_certification", "status", "published"], + ) def get_certificates(member=None): - return frappe.get_all("LMS Certificate", { - "member": member or frappe.session.user - }, ["course", "member", "issue_date", "expiry_date", "name"]) + return frappe.get_all( + "LMS Certificate", + {"member": member or frappe.session.user}, + ["course", "member", "issue_date", "expiry_date", "name"], + ) def validate_image(path): - if path and "/private" in path: - file = frappe.get_doc("File", {"file_url": path}) - file.is_private = 0 - file.save(ignore_permissions=True) - return file.file_url - return path + if path and "/private" in path: + file = frappe.get_doc("File", {"file_url": path}) + file.is_private = 0 + file.save(ignore_permissions=True) + return file.file_url + return path def create_notification_log(doc, method): - topic = frappe.db.get_value("Discussion Topic", doc.topic, - ["reference_doctype", "reference_docname", "owner", "title"], as_dict=1) + topic = frappe.db.get_value( + "Discussion Topic", + doc.topic, + ["reference_doctype", "reference_docname", "owner", "title"], + as_dict=1, + ) - if topic.reference_doctype != "Course Lesson": - return + if topic.reference_doctype != "Course Lesson": + return - course = frappe.db.get_value("Course Lesson", topic.reference_docname, "course") - instructors = frappe.db.get_all("Course Instructor", { "parent": course }, pluck="instructor") + course = frappe.db.get_value("Course Lesson", topic.reference_docname, "course") + instructors = frappe.db.get_all( + "Course Instructor", {"parent": course}, pluck="instructor" + ) - notification = frappe._dict({ - "subject": _("New reply on the topic {0}").format(topic.title), - "email_content": doc.reply, - "document_type": topic.reference_doctype, - "document_name": topic.reference_docname, - "for_user": topic.owner, - "from_user": doc.owner, - "type": "Alert" - }) + notification = frappe._dict( + { + "subject": _("New reply on the topic {0}").format(topic.title), + "email_content": doc.reply, + "document_type": topic.reference_doctype, + "document_name": topic.reference_docname, + "for_user": topic.owner, + "from_user": doc.owner, + "type": "Alert", + } + ) - users = [] - if doc.owner != topic.owner: - users.append(topic.owner) + users = [] + if doc.owner != topic.owner: + users.append(topic.owner) - if doc.owner not in instructors: - users += instructors - make_notification_logs(notification, users) + if doc.owner not in instructors: + users += instructors + make_notification_logs(notification, users) def get_lesson_count(course): - lesson_count = 0 - chapters = frappe.get_all("Chapter Reference", {"parent": course}, ["chapter"]) - for chapter in chapters: - lesson_count += frappe.db.count("Lesson Reference", {"parent": chapter.chapter}) + lesson_count = 0 + chapters = frappe.get_all("Chapter Reference", {"parent": course}, ["chapter"]) + for chapter in chapters: + lesson_count += frappe.db.count("Lesson Reference", {"parent": chapter.chapter}) - return lesson_count + return lesson_count def check_profile_restriction(): - return frappe.db.get_single_value("LMS Settings", "force_profile_completion") + return frappe.db.get_single_value("LMS Settings", "force_profile_completion") def get_restriction_details(): - user = frappe.db.get_value("User", frappe.session.user, ["profile_complete", "username"], as_dict=True) - return { - "restrict": not user.profile_complete, - "username": user.username, - "prefix": frappe.get_hooks("profile_url_prefix")[0] or "/users/" - } + user = frappe.db.get_value( + "User", frappe.session.user, ["profile_complete", "username"], as_dict=True + ) + return { + "restrict": not user.profile_complete, + "username": user.username, + "prefix": frappe.get_hooks("profile_url_prefix")[0] or "/users/", + } def get_all_memberships(member): - return frappe.get_all("LMS Batch Membership", { - "member": member - }, ["name", "course", "batch", "current_lesson", "member_type", "progress"]) + return frappe.get_all( + "LMS Batch Membership", + {"member": member}, + ["name", "course", "batch", "current_lesson", "member_type", "progress"], + ) def get_filtered_membership(course, memberships): - current_membership = list(filter(lambda x: x.course == course, memberships)) - return current_membership[0] if len(current_membership) else None + current_membership = list(filter(lambda x: x.course == course, memberships)) + return current_membership[0] if len(current_membership) else None def show_start_learing_cta(course, membership): - return not course.disable_self_learning and not membership and not course.upcoming \ - and not check_profile_restriction() and not is_instructor(course.name) and course.status == "Approved" + return ( + not course.disable_self_learning + and not membership + and not course.upcoming + and not check_profile_restriction() + and not is_instructor(course.name) + and course.status == "Approved" + ) @frappe.whitelist(allow_guest=True) def get_chart_data(chart_name, timespan, timegrain, from_date, to_date): - chart = frappe.get_doc("Dashboard Chart", chart_name) - filters = [([chart.document_type, "docstatus", "<", 2, False])] - doctype = chart.document_type - datefield = chart.based_on - value_field = chart.value_based_on or "1" - from_date = get_datetime(from_date).strftime("%Y-%m-%d") - to_date = get_datetime(to_date) + chart = frappe.get_doc("Dashboard Chart", chart_name) + filters = [([chart.document_type, "docstatus", "<", 2, False])] + doctype = chart.document_type + datefield = chart.based_on + value_field = chart.value_based_on or "1" + from_date = get_datetime(from_date).strftime("%Y-%m-%d") + to_date = get_datetime(to_date) - filters.append([doctype, datefield, ">=", from_date, False]) - filters.append([doctype, datefield, "<=", to_date, False]) + filters.append([doctype, datefield, ">=", from_date, False]) + filters.append([doctype, datefield, "<=", to_date, False]) - data = frappe.db.get_all( - doctype, - fields=[f"{datefield} as _unit", f"SUM({value_field})", "COUNT(*)"], - filters=filters, - group_by="_unit", - order_by="_unit asc", - as_list=True, - ) + data = frappe.db.get_all( + doctype, + fields=[f"{datefield} as _unit", f"SUM({value_field})", "COUNT(*)"], + filters=filters, + group_by="_unit", + order_by="_unit asc", + as_list=True, + ) - result = get_result(data, timegrain, from_date, to_date, chart.chart_type) + result = get_result(data, timegrain, from_date, to_date, chart.chart_type) - return { - "labels": [ - format_date(get_period(r[0], timegrain), parse_day_first=True) - if timegrain in ("Daily", "Weekly") - else get_period(r[0], timegrain) - for r in result - ], - "datasets": [{"name": chart.name, "values": [r[1] for r in result]}], - } + return { + "labels": [ + format_date(get_period(r[0], timegrain), parse_day_first=True) + if timegrain in ("Daily", "Weekly") + else get_period(r[0], timegrain) + for r in result + ], + "datasets": [{"name": chart.name, "values": [r[1] for r in result]}], + } diff --git a/lms/lms/web_form/add_a_new_batch/add_a_new_batch.js b/lms/lms/web_form/add_a_new_batch/add_a_new_batch.js index 047429b8..4101ec29 100644 --- a/lms/lms/web_form/add_a_new_batch/add_a_new_batch.js +++ b/lms/lms/web_form/add_a_new_batch/add_a_new_batch.js @@ -1,39 +1,49 @@ frappe.ready(function () { - frappe.web_form.after_save = () => { - let data = frappe.web_form.get_values(); - let slug = new URLSearchParams(window.location.search).get("slug") - frappe.msgprint({ - message: __("Batch {0} has been successfully created!", [data.title]), - clear: true - }); - setTimeout(function () { - window.location.href = `courses/${slug}`; - }, 2000); - } + frappe.web_form.after_save = () => { + let data = frappe.web_form.get_values(); + let slug = new URLSearchParams(window.location.search).get("slug"); + frappe.msgprint({ + message: __("Batch {0} has been successfully created!", [ + data.title, + ]), + clear: true, + }); + setTimeout(function () { + window.location.href = `courses/${slug}`; + }, 2000); + }; - frappe.web_form.validate = () => { - let sysdefaults = frappe.boot.sysdefaults; - let time_format = sysdefaults && sysdefaults.time_format ? sysdefaults.time_format : 'HH:mm:ss'; - let data = frappe.web_form.get_values(); + frappe.web_form.validate = () => { + let sysdefaults = frappe.boot.sysdefaults; + let time_format = + sysdefaults && sysdefaults.time_format + ? sysdefaults.time_format + : "HH:mm:ss"; + let data = frappe.web_form.get_values(); - data.start_time = moment(data.start_time, time_format).format(time_format) - data.end_time = moment(data.end_time, time_format).format(time_format) + data.start_time = moment(data.start_time, time_format).format( + time_format + ); + data.end_time = moment(data.end_time, time_format).format(time_format); - if (data.start_date < frappe.datetime.nowdate()) { - frappe.msgprint(__('Start date cannot be a past date.')) - return false; - } + if (data.start_date < frappe.datetime.nowdate()) { + frappe.msgprint(__("Start date cannot be a past date.")); + return false; + } - if (!frappe.datetime.validate(data.start_time) || !frappe.datetime.validate(data.end_time)) { - frappe.msgprint(__('Invalid Start or End Time.')); - return false; - } + if ( + !frappe.datetime.validate(data.start_time) || + !frappe.datetime.validate(data.end_time) + ) { + frappe.msgprint(__("Invalid Start or End Time.")); + return false; + } - if (data.start_time > data.end_time) { - frappe.msgprint(__('Start Time should be less than End Time.')); - return false; - } + if (data.start_time > data.end_time) { + frappe.msgprint(__("Start Time should be less than End Time.")); + return false; + } - return true; - }; -}) + return true; + }; +}); diff --git a/lms/lms/web_form/add_a_new_batch/add_a_new_batch.py b/lms/lms/web_form/add_a_new_batch/add_a_new_batch.py index 96e8cb34..80b7b873 100644 --- a/lms/lms/web_form/add_a_new_batch/add_a_new_batch.py +++ b/lms/lms/web_form/add_a_new_batch/add_a_new_batch.py @@ -1,7 +1,6 @@ -from __future__ import unicode_literals - import frappe + def get_context(context): - # do your magic here - pass + # do your magic here + pass diff --git a/lms/lms/web_form/profile/profile.js b/lms/lms/web_form/profile/profile.js index ea12f0ef..66bd3af9 100644 --- a/lms/lms/web_form/profile/profile.js +++ b/lms/lms/web_form/profile/profile.js @@ -1,85 +1,96 @@ frappe.ready(function () { + frappe.web_form.after_load = () => { + redirect_to_user_profile_form(); + add_listener_for_current_company(); + add_listener_for_certificate_expiry(); + add_listener_for_skill_add_rows(); + add_listener_for_functions_add_rows(); + add_listener_for_industries_add_rows(); + }; - frappe.web_form.after_load = () => { - - redirect_to_user_profile_form(); - add_listener_for_current_company(); - add_listener_for_certificate_expiry(); - add_listener_for_skill_add_rows(); - add_listener_for_functions_add_rows(); - add_listener_for_industries_add_rows(); - - } - - frappe.web_form.validate = () => { - let information_missing; - const data = frappe.web_form.get_values(); - if (data && data.work_experience && data.work_experience.length) { - data.work_experience.forEach(exp => { - if (!exp.current && !exp.to_date) { - information_missing = true - frappe.msgprint('To Date is mandatory in Work Experience.'); + frappe.web_form.validate = () => { + let information_missing; + const data = frappe.web_form.get_values(); + if (data && data.work_experience && data.work_experience.length) { + data.work_experience.forEach((exp) => { + if (!exp.current && !exp.to_date) { + information_missing = true; + frappe.msgprint("To Date is mandatory in Work Experience."); + } + }); } - }); - } - if (information_missing) - return false; - return true; - }; + if (information_missing) return false; + return true; + }; - frappe.web_form.after_save = () => { - setTimeout(() => { - window.location.href = `/profile_/${frappe.web_form.get_value(["username"])}`; - }) - } + frappe.web_form.after_save = () => { + setTimeout(() => { + window.location.href = `/profile_/${frappe.web_form.get_value([ + "username", + ])}`; + }); + }; }); const redirect_to_user_profile_form = () => { - if (!frappe.utils.get_url_arg("name")) { - window.location.href = `/edit-profile?name=${frappe.session.user}`; - } + if (!frappe.utils.get_url_arg("name")) { + window.location.href = `/edit-profile?name=${frappe.session.user}`; + } }; - const add_listener_for_current_company = () => { - $(document).on("click", "input[data-fieldname='current']", (e) => { - if ($(e.currentTarget).prop("checked")) - $("div[data-fieldname='to_date']").addClass("hide"); - else - $("div[data-fieldname='to_date']").removeClass("hide"); - }); + $(document).on("click", "input[data-fieldname='current']", (e) => { + if ($(e.currentTarget).prop("checked")) + $("div[data-fieldname='to_date']").addClass("hide"); + else $("div[data-fieldname='to_date']").removeClass("hide"); + }); }; const add_listener_for_certificate_expiry = () => { - $(document).on("click", "input[data-fieldname='expire']", (e) => { - if ($(e.currentTarget).prop("checked")) - $("div[data-fieldname='expiration_date']").addClass("hide"); - else - $("div[data-fieldname='expiration_date']").removeClass("hide"); - }); + $(document).on("click", "input[data-fieldname='expire']", (e) => { + if ($(e.currentTarget).prop("checked")) + $("div[data-fieldname='expiration_date']").addClass("hide"); + else $("div[data-fieldname='expiration_date']").removeClass("hide"); + }); }; const add_listener_for_skill_add_rows = () => { - $('[data-fieldname="skill"]').find(".grid-add-row").click((e) => { - if ($('[data-fieldname="skill"]').find(".grid-row").length > 5) { - $('[data-fieldname="skill"]').find(".grid-add-row").hide(); - } - }); + $('[data-fieldname="skill"]') + .find(".grid-add-row") + .click((e) => { + if ($('[data-fieldname="skill"]').find(".grid-row").length > 5) { + $('[data-fieldname="skill"]').find(".grid-add-row").hide(); + } + }); }; const add_listener_for_functions_add_rows = () => { - $('[data-fieldname="preferred_functions"]').find(".grid-add-row").click((e) => { - if ($('[data-fieldname="preferred_functions"]').find(".grid-row").length > 3) { - $('[data-fieldname="preferred_functions"]').find(".grid-add-row").hide(); - } - }); + $('[data-fieldname="preferred_functions"]') + .find(".grid-add-row") + .click((e) => { + if ( + $('[data-fieldname="preferred_functions"]').find(".grid-row") + .length > 3 + ) { + $('[data-fieldname="preferred_functions"]') + .find(".grid-add-row") + .hide(); + } + }); }; const add_listener_for_industries_add_rows = () => { - $('[data-fieldname="preferred_industries"]').find(".grid-add-row").click((e) => { - if ($('[data-fieldname="preferred_industries"]').find(".grid-row").length > 3) { - $('[data-fieldname="preferred_industries"]').find(".grid-add-row").hide(); - } - }); + $('[data-fieldname="preferred_industries"]') + .find(".grid-add-row") + .click((e) => { + if ( + $('[data-fieldname="preferred_industries"]').find(".grid-row") + .length > 3 + ) { + $('[data-fieldname="preferred_industries"]') + .find(".grid-add-row") + .hide(); + } + }); }; diff --git a/lms/lms/web_form/profile/profile.py b/lms/lms/web_form/profile/profile.py index e1ada619..80b7b873 100644 --- a/lms/lms/web_form/profile/profile.py +++ b/lms/lms/web_form/profile/profile.py @@ -1,5 +1,6 @@ import frappe + def get_context(context): # do your magic here pass diff --git a/lms/overrides/test_user.py b/lms/overrides/test_user.py index 35a37ec8..5d1b9c03 100644 --- a/lms/overrides/test_user.py +++ b/lms/overrides/test_user.py @@ -2,43 +2,43 @@ # See license.txt import unittest + import frappe + from lms.lms.doctype.lms_course.test_lms_course import new_user class TestCustomUser(unittest.TestCase): + def test_with_basic_username(self): + user = new_user("Username", "test_with_basic_username@example.com") + self.assertEqual(user.username, "username") - def test_with_basic_username(self): - user = new_user("Username", "test_with_basic_username@example.com") - self.assertEqual(user.username, "username") + def test_without_username(self): + """The user in this test has the same first name as the user of the test test_with_basic_username. + In such cases frappe makes the username of the second user empty. + The condition in lms app should override this and save a username.""" + user = new_user("Username", "test-without-username@example.com") + self.assertTrue(user.username) - def test_without_username(self): - """ The user in this test has the same first name as the user of the test test_with_basic_username. - In such cases frappe makes the username of the second user empty. - The condition in lms app should override this and save a username. """ - user = new_user("Username", "test-without-username@example.com") - self.assertTrue(user.username) + def test_with_illegal_characters(self): + user = new_user("Username$$", "test_with_illegal_characters@example.com") + self.assertEqual(user.username[:8], "username") - def test_with_illegal_characters(self): - user = new_user("Username$$", "test_with_illegal_characters@example.com") - self.assertEqual(user.username[:8], "username") + def test_with_underscore_at_end(self): + user = new_user("Username___", "test_with_underscore_at_end@example.com") + self.assertNotEqual(user.username[-1], "_") - def test_with_underscore_at_end(self): - user = new_user("Username___", "test_with_underscore_at_end@example.com") - self.assertNotEqual(user.username[-1], "_") + def test_with_short_first_name(self): + user = new_user("USN", "test_with_short_first_name@example.com") + self.assertGreaterEqual(len(user.username), 4) - - def test_with_short_first_name(self): - user = new_user("USN", "test_with_short_first_name@example.com") - self.assertGreaterEqual(len(user.username), 4) - - @classmethod - def tearDownClass(cls) -> None: - users = [ - "test_with_basic_username@example.com", - "test-without-username@example.com", - "test_with_illegal_characters@example.com", - "test_with_underscore_at_end@example.com", - "test_with_short_first_name@example.com" - ] - frappe.db.delete("User", {"name": ["in", users]}) + @classmethod + def tearDownClass(cls) -> None: + users = [ + "test_with_basic_username@example.com", + "test-without-username@example.com", + "test_with_illegal_characters@example.com", + "test_with_underscore_at_end@example.com", + "test_with_short_first_name@example.com", + ] + frappe.db.delete("User", {"name": ["in", users]}) diff --git a/lms/overrides/user.py b/lms/overrides/user.py index 32f22f07..0c3b0d51 100644 --- a/lms/overrides/user.py +++ b/lms/overrides/user.py @@ -1,319 +1,359 @@ -import frappe -from frappe.core.doctype.user.user import User -from frappe.utils import cint, escape_html, random_string import hashlib import random import re -from frappe import _ -from frappe.website.utils import is_signup_disabled -from lms.lms.utils import validate_image + +import frappe import requests +from frappe import _ +from frappe.core.doctype.user.user import User +from frappe.utils import cint, escape_html, random_string +from frappe.website.utils import is_signup_disabled + +from lms.lms.utils import validate_image from lms.widgets import Widgets class CustomUser(User): + def validate(self): + super().validate() + self.validate_username_characters() + self.validate_completion() + self.user_image = validate_image(self.user_image) + self.cover_image = validate_image(self.cover_image) + def validate_username_characters(self): + if self.username and len(self.username): + other_conditions = ( + self.username[0] == "_" or self.username[-1] == "_" or "-" in self.username + ) + else: + other_conditions = "" - def validate(self): - super(CustomUser, self).validate() - self.validate_username_characters() - self.validate_completion() - self.user_image = validate_image(self.user_image) - self.cover_image = validate_image(self.cover_image) + regex = re.compile(r"[@!#$%^&*()<>?/\|}{~:-]") + if self.is_new(): + if not self.username: + self.username = self.get_username_from_first_name() - def validate_username_characters(self): - if self.username and len(self.username): - other_conditions = self.username[0] == "_" or self.username[-1] == "_" or "-" in self.username - else: - other_conditions = '' + if self.username.find(" "): + self.username.replace(" ", "") - regex = re.compile('[@!#$%^&*()<>?/\|}{~:-]') + if len(self.username) < 4: + self.username = self.email.replace("@", "").replace(".", "") - if self.is_new(): - if not self.username: - self.username = self.get_username_from_first_name() + if regex.search(self.username) or other_conditions: + self.username = self.remove_illegal_characters() - if self.username.find(" "): - self.username.replace(" ", "") + while self.username_exists(): + self.username = self.remove_illegal_characters() + str(random.randint(0, 99)) - if len(self.username) < 4: - self.username = self.email.replace("@", "").replace(".", "") + else: + if not self.username: + frappe.throw(_("Username already exists.")) - if regex.search(self.username) or other_conditions: - self.username = self.remove_illegal_characters() + if regex.search(self.username): + frappe.throw(_("Username can only contain alphabets, numbers and underscore.")) - while self.username_exists(): - self.username = self.remove_illegal_characters() + str(random.randint(0, 99)) + if other_conditions: + if "-" in self.username: + frappe.throw(_("Username cannot contain a Hyphen(-)")) + else: + frappe.throw(_("First and Last character of username cannot be Underscore(_).")) - else: - if not self.username: - frappe.throw(_("Username already exists.")) + if len(self.username) < 4: + frappe.throw(_("Username cannot be less than 4 characters")) - if regex.search(self.username): - frappe.throw(_("Username can only contain alphabets, numbers and underscore.")) + def get_username_from_first_name(self): + return frappe.scrub(self.first_name) + str(random.randint(0, 99)) - if other_conditions: - if "-" in self.username: - frappe.throw(_("Username cannot contain a Hyphen(-)")) - else: - frappe.throw(_("First and Last character of username cannot be Underscore(_).")) + def remove_illegal_characters(self): + return re.sub(r"[^\w]+", "", self.username).strip("_") - if len(self.username) < 4: - frappe.throw(_("Username cannot be less than 4 characters")) + def validate_skills(self): + unique_skills = [] + for skill in self.skill: + if not skill.skill_name: + return + if not skill.skill_name in unique_skills: + unique_skills.append(skill.skill_name) + else: + frappe.throw(_("Skills must be unique")) - def get_username_from_first_name(self): - return frappe.scrub(self.first_name) + str(random.randint(0, 99)) + def validate_completion(self): + if frappe.db.get_single_value("LMS Settings", "force_profile_completion"): + all_fields_have_value = True + profile_mandatory_fields = frappe.get_hooks("profile_mandatory_fields") + docfields = frappe.get_meta(self.doctype).fields - def remove_illegal_characters(self): - return re.sub("[^\w]+", "", self.username).strip("_") + for field in profile_mandatory_fields: + if not self.get(field): + all_fields_have_value = False + break - def validate_skills(self): - unique_skills = [] - for skill in self.skill: - if not skill.skill_name: - return - if not skill.skill_name in unique_skills: - unique_skills.append(skill.skill_name) - else: - frappe.throw(_("Skills must be unique")) + self.profile_complete = all_fields_have_value - def validate_completion(self): - if frappe.db.get_single_value("LMS Settings", "force_profile_completion"): - all_fields_have_value = True - profile_mandatory_fields = frappe.get_hooks("profile_mandatory_fields") - docfields = frappe.get_meta(self.doctype).fields + def get_batch_count(self) -> int: + """Returns the number of batches authored by this user.""" + return frappe.db.count( + "LMS Batch Membership", {"member": self.name, "member_type": "Mentor"} + ) - for field in profile_mandatory_fields: - if not self.get(field): - all_fields_have_value = False - break + def get_user_reviews(self): + """Returns the reviews created by user""" + return frappe.get_all("LMS Course Review", {"owner": self.name}) - self.profile_complete = all_fields_have_value + def get_mentored_courses(self): + """Returns all courses mentored by this user""" + mentored_courses = [] + mapping = frappe.get_all( + "LMS Course Mentor Mapping", + { + "mentor": self.name, + }, + ["name", "course"], + ) + for map in mapping: + if frappe.db.get_value("LMS Course", map.course, "published"): + course = frappe.db.get_value( + "LMS Course", + map.course, + ["name", "upcoming", "title", "image", "enable_certification"], + as_dict=True, + ) + mentored_courses.append(course) - - def get_batch_count(self) -> int: - """Returns the number of batches authored by this user. - """ - return frappe.db.count( - 'LMS Batch Membership', { - 'member': self.name, - 'member_type': 'Mentor' - }) - - def get_user_reviews(self): - """ Returns the reviews created by user """ - return frappe.get_all("LMS Course Review", - { - "owner": self.name - }) - - def get_mentored_courses(self): - """ Returns all courses mentored by this user """ - mentored_courses = [] - mapping = frappe.get_all("LMS Course Mentor Mapping", - { - "mentor": self.name, - }, - ["name", "course"] - ) - - for map in mapping: - if frappe.db.get_value("LMS Course", map.course, "published"): - course = frappe.db.get_value("LMS Course", map.course, - ["name", "upcoming", "title", "image", "enable_certification"], as_dict=True) - mentored_courses.append(course) - - return mentored_courses + return mentored_courses def get_enrolled_courses(): - in_progress = [] - completed = [] - memberships = get_course_membership(None, member_type="Student") + in_progress = [] + completed = [] + memberships = get_course_membership(None, member_type="Student") - for membership in memberships: - course = frappe.db.get_value("LMS Course", membership.course, ["name", "upcoming", "title", "image", - "enable_certification", "paid_certificate", "price_certificate", "currency", "published"], as_dict=True) - if not course.published: - continue - progress = cint(membership.progress) - if progress < 100: - in_progress.append(course) - else: - completed.append(course) + for membership in memberships: + course = frappe.db.get_value( + "LMS Course", + membership.course, + [ + "name", + "upcoming", + "title", + "image", + "enable_certification", + "paid_certificate", + "price_certificate", + "currency", + "published", + ], + as_dict=True, + ) + if not course.published: + continue + progress = cint(membership.progress) + if progress < 100: + in_progress.append(course) + else: + completed.append(course) + + return {"in_progress": in_progress, "completed": completed} - return { - "in_progress": in_progress, - "completed": completed - } def get_course_membership(member=None, member_type=None): - """ Returns all memberships of the user. """ + """Returns all memberships of the user.""" - filters = { - "member": member or frappe.session.user - } - if member_type: - filters["member_type"] = member_type + filters = {"member": member or frappe.session.user} + if member_type: + filters["member_type"] = member_type - return frappe.get_all("LMS Batch Membership", filters, ["name", "course", "progress"]) + return frappe.get_all("LMS Batch Membership", filters, ["name", "course", "progress"]) def get_authored_courses(member=None, only_published=True): - """ Returns the number of courses authored by this user. """ - course_details = [] - courses = frappe.get_all("Course Instructor", { - "instructor": member or frappe.session.user - }, ["parent"]) + """Returns the number of courses authored by this user.""" + course_details = [] + courses = frappe.get_all( + "Course Instructor", {"instructor": member or frappe.session.user}, ["parent"] + ) - for course in courses: - detail = frappe.db.get_value("LMS Course", course.parent, - ["name", "upcoming", "title", "image", "enable_certification", "status", "published"], as_dict=True) + for course in courses: + detail = frappe.db.get_value( + "LMS Course", + course.parent, + [ + "name", + "upcoming", + "title", + "image", + "enable_certification", + "status", + "published", + ], + as_dict=True, + ) - if only_published and detail and not detail.published: - continue - course_details.append(detail) + if only_published and detail and not detail.published: + continue + course_details.append(detail) - return course_details + return course_details def get_palette(full_name): - """ - Returns a color unique to each member for Avatar """ + """ + Returns a color unique to each member for Avatar""" - palette = [ - ['--orange-avatar-bg', '--orange-avatar-color'], - ['--pink-avatar-bg', '--pink-avatar-color'], - ['--blue-avatar-bg', '--blue-avatar-color'], - ['--green-avatar-bg', '--green-avatar-color'], - ['--dark-green-avatar-bg', '--dark-green-avatar-color'], - ['--red-avatar-bg', '--red-avatar-color'], - ['--yellow-avatar-bg', '--yellow-avatar-color'], - ['--purple-avatar-bg', '--purple-avatar-color'], - ['--gray-avatar-bg', '--gray-avatar-color0'] - ] + palette = [ + ["--orange-avatar-bg", "--orange-avatar-color"], + ["--pink-avatar-bg", "--pink-avatar-color"], + ["--blue-avatar-bg", "--blue-avatar-color"], + ["--green-avatar-bg", "--green-avatar-color"], + ["--dark-green-avatar-bg", "--dark-green-avatar-color"], + ["--red-avatar-bg", "--red-avatar-color"], + ["--yellow-avatar-bg", "--yellow-avatar-color"], + ["--purple-avatar-bg", "--purple-avatar-color"], + ["--gray-avatar-bg", "--gray-avatar-color0"], + ] + + encoded_name = str(full_name).encode("utf-8") + hash_name = hashlib.md5(encoded_name).hexdigest() + idx = cint((int(hash_name[4:6], 16) + 1) / 5.33) + return palette[idx % 8] - encoded_name = str(full_name).encode("utf-8") - hash_name = hashlib.md5(encoded_name).hexdigest() - idx = cint((int(hash_name[4:6], 16) + 1) / 5.33) - return palette[idx % 8] @frappe.whitelist(allow_guest=True) def sign_up(email, full_name, verify_terms, user_category): - if is_signup_disabled(): - frappe.throw(_('Sign Up is disabled'), title='Not Allowed') + if is_signup_disabled(): + frappe.throw(_("Sign Up is disabled"), title="Not Allowed") - user = frappe.db.get("User", {"email": email}) - if user: - if user.enabled: - return 0, _("Already Registered") - else: - return 0, _("Registered but disabled") - else: - if frappe.db.get_creation_count('User', 60) > 300: - frappe.respond_as_web_page(_('Temporarily Disabled'), - _('Too many users signed up recently, so the registration is disabled. Please try back in an hour'), - http_status_code=429) + user = frappe.db.get("User", {"email": email}) + if user: + if user.enabled: + return 0, _("Already Registered") + else: + return 0, _("Registered but disabled") + else: + if frappe.db.get_creation_count("User", 60) > 300: + frappe.respond_as_web_page( + _("Temporarily Disabled"), + _( + "Too many users signed up recently, so the registration is disabled. Please try back in an hour" + ), + http_status_code=429, + ) - user = frappe.get_doc({ - "doctype":"User", - "email": email, - "first_name": escape_html(full_name), - "verify_terms": verify_terms, - "user_category": user_category, - "country": "", - "enabled": 1, - "new_password": random_string(10), - "user_type": "Website User" - }) - user.flags.ignore_permissions = True - user.flags.ignore_password_policy = True - user.insert() - set_country_from_ip(None, user.name) + user = frappe.get_doc( + { + "doctype": "User", + "email": email, + "first_name": escape_html(full_name), + "verify_terms": verify_terms, + "user_category": user_category, + "country": "", + "enabled": 1, + "new_password": random_string(10), + "user_type": "Website User", + } + ) + user.flags.ignore_permissions = True + user.flags.ignore_password_policy = True + user.insert() + set_country_from_ip(None, user.name) - # set default signup role as per Portal Settings - default_role = frappe.db.get_value("Portal Settings", None, "default_role") - if default_role: - user.add_roles(default_role) + # set default signup role as per Portal Settings + default_role = frappe.db.get_value("Portal Settings", None, "default_role") + if default_role: + user.add_roles(default_role) - if user.flags.email_sent: - return 1, _("Please check your email for verification") - else: - return 2, _("Please ask your administrator to verify your sign-up") + if user.flags.email_sent: + return 1, _("Please check your email for verification") + else: + return 2, _("Please ask your administrator to verify your sign-up") def set_country_from_ip(login_manager=None, user=None): - if not user and login_manager: - user = login_manager.user + if not user and login_manager: + user = login_manager.user + + user_country = frappe.db.get_value("User", user, "country") + # if user_country: + # return + frappe.db.set_value("User", user, "country", get_country_code()) + return - user_country = frappe.db.get_value("User", user, "country") - #if user_country: - # return - frappe.db.set_value("User", user, "country", get_country_code()) - return def get_country_code(): - ip = frappe.local.request_ip - res = requests.get("http://ip-api.com/json/{ip}".format(ip=ip)) + ip = frappe.local.request_ip + res = requests.get(f"http://ip-api.com/json/{ip}") + + try: + data = res.json() + if data.get("status") != "fail": + return frappe.db.get_value("Country", {"code": data.get("countryCode")}, "name") + except Exception: + pass + return - try: - data = res.json() - if data.get("status") != "fail": - return frappe.db.get_value("Country", {"code": data.get("countryCode")}, "name") - except Exception: - pass - return @frappe.whitelist(allow_guest=True) def search_users(start=0, text=""): - or_filters = get_or_filters(text) - count = len(get_users(or_filters, 0, 900000000, text)) - users = get_users(or_filters, start, 24, text) - user_details = get_user_details(users) + or_filters = get_or_filters(text) + count = len(get_users(or_filters, 0, 900000000, text)) + users = get_users(or_filters, start, 24, text) + user_details = get_user_details(users) + + return {"user_details": user_details, "start": cint(start) + 24, "count": count} - return { - "user_details": user_details, - "start": cint(start) + 24, - "count": count - } def get_or_filters(text): - user_fields = ["first_name", "last_name", "full_name", "email", "preferred_location", "dream_companies"] - education_fields = ["institution_name", "location", "degree_type", "major"] - work_fields = ["title", "company"] - certification_fields = ["certification_name", "organization"] + user_fields = [ + "first_name", + "last_name", + "full_name", + "email", + "preferred_location", + "dream_companies", + ] + education_fields = ["institution_name", "location", "degree_type", "major"] + work_fields = ["title", "company"] + certification_fields = ["certification_name", "organization"] - or_filters = [] - if text: - for field in user_fields: - or_filters.append(f"u.{field} like '%{text}%'") - for field in education_fields: - or_filters.append(f"ed.{field} like '%{text}%'") - for field in work_fields: - or_filters.append(f"we.{field} like '%{text}%'") - for field in certification_fields: - or_filters.append(f"c.{field} like '%{text}%'") + or_filters = [] + if text: + for field in user_fields: + or_filters.append(f"u.{field} like '%{text}%'") + for field in education_fields: + or_filters.append(f"ed.{field} like '%{text}%'") + for field in work_fields: + or_filters.append(f"we.{field} like '%{text}%'") + for field in certification_fields: + or_filters.append(f"c.{field} like '%{text}%'") - or_filters.append(f"s.skill_name like '%{text}%'") - or_filters.append(f"pf.function like '%{text}%'") - or_filters.append(f"pi.industry like '%{text}%'") + or_filters.append(f"s.skill_name like '%{text}%'") + or_filters.append(f"pf.function like '%{text}%'") + or_filters.append(f"pi.industry like '%{text}%'") + return "AND ({})".format(" OR ".join(or_filters)) if or_filters else "" - return "AND ({})".format(" OR ".join(or_filters)) if or_filters else "" def get_user_details(users): - user_details = [] - for user in users: - details = frappe.db.get_value("User", user, ["name", "username", "full_name", "user_image", "headline"], as_dict=True) - user_details.append(Widgets().MemberCard(member=details, avatar_class="avatar-large")) + user_details = [] + for user in users: + details = frappe.db.get_value( + "User", + user, + ["name", "username", "full_name", "user_image", "headline"], + as_dict=True, + ) + user_details.append(Widgets().MemberCard(member=details, avatar_class="avatar-large")) + + return user_details - return user_details def get_users(or_filters, start, page_length, text): - users = frappe.db.sql(""" + users = frappe.db.sql( + """ SELECT DISTINCT u.name FROM `tabUser` u LEFT JOIN `tabEducation Detail` ed @@ -331,25 +371,28 @@ def get_users(or_filters, start, page_length, text): WHERE u.enabled = True {or_filters} ORDER BY u.creation desc LIMIT {start}, {page_length} - """.format(or_filters = or_filters, start=start, page_length=page_length), as_dict=1) + """.format( + or_filters=or_filters, start=start, page_length=page_length + ), + as_dict=1, + ) - return users + return users @frappe.whitelist() def save_role(user, role, value): - if cint(value): - doc = frappe.get_doc({ - "doctype": "Has Role", - "parent": user, - "role": role, - "parenttype": "User", - "parentfield": "roles" - }) - doc.save(ignore_permissions=True) - else: - frappe.db.delete("Has Role", { - "parent": user, - "role": role - }) - return True + if cint(value): + doc = frappe.get_doc( + { + "doctype": "Has Role", + "parent": user, + "role": role, + "parenttype": "User", + "parentfield": "roles", + } + ) + doc.save(ignore_permissions=True) + else: + frappe.db.delete("Has Role", {"parent": user, "role": role}) + return True diff --git a/lms/overrides/web_template.py b/lms/overrides/web_template.py index 83b3cae6..beea46bc 100644 --- a/lms/overrides/web_template.py +++ b/lms/overrides/web_template.py @@ -1,15 +1,17 @@ -import frappe -from frappe.website.doctype.web_template.web_template import WebTemplate -from lms.widgets import Widgets import json -class CustomWebTemplate(WebTemplate): +import frappe +from frappe.website.doctype.web_template.web_template import WebTemplate - def render(self, values=None): - if not values: - values = {} - values = frappe.parse_json(values) - values.update({"values": values}) - values.update({"widgets": Widgets()}) - template = self.get_template(self.standard) - return frappe.render_template(template, values) +from lms.widgets import Widgets + + +class CustomWebTemplate(WebTemplate): + def render(self, values=None): + if not values: + values = {} + values = frappe.parse_json(values) + values.update({"values": values}) + values.update({"widgets": Widgets()}) + template = self.get_template(self.standard) + return frappe.render_template(template, values) diff --git a/lms/page_renderers.py b/lms/page_renderers.py index f16df720..5a067d8a 100644 --- a/lms/page_renderers.py +++ b/lms/page_renderers.py @@ -3,10 +3,9 @@ Handles rendering of profile pages. """ import re + import frappe from frappe.website.page_renderers.base_renderer import BaseRenderer -from frappe.website.page_renderers.template_page import TemplatePage - from frappe.website.page_renderers.document_page import DocumentPage from frappe.website.page_renderers.list_page import ListPage from frappe.website.page_renderers.not_found_page import NotFoundPage @@ -18,87 +17,93 @@ from frappe.website.page_renderers.web_form import WebFormPage def get_profile_url(username): - """Returns the profile URL given username. + """Returns the profile URL given username. - The default URL prefix for profiles is /users, but tha can be customized. + The default URL prefix for profiles is /users, but tha can be customized. + + This functions looks at the current value from the config and generates + the URL for the profile. + """ + return get_profile_url_prefix() + username - This functions looks at the current value from the config and generates - the URL for the profile. - """ - return get_profile_url_prefix() + username def get_profile_url_prefix(): - hooks = frappe.get_hooks("profile_url_prefix") or ["/users/"] - return hooks[-1] + hooks = frappe.get_hooks("profile_url_prefix") or ["/users/"] + return hooks[-1] + RE_INVALID_USERNAME = re.compile("[@!#$%^&*()<>?/\\|}{~:-]") + class ProfileRedirectPage(BaseRenderer): - """Renderer to redirect /profile_/foo to /foo. + """Renderer to redirect /profile_/foo to /foo. - This is useful to redirect to profile pages from javascript as there is no - easy to find the profile prefix. - """ - def can_render(self): - return self.path.startswith("profile_/") + This is useful to redirect to profile pages from javascript as there is no + easy to find the profile prefix. + """ + + def can_render(self): + return self.path.startswith("profile_/") + + def render(self): + username = self.path[len("profile_/") :] + frappe.flags.redirect_location = get_profile_url_prefix() + username + return RedirectPage(self.path).render() - def render(self): - username = self.path[len("profile_/"):] - frappe.flags.redirect_location = get_profile_url_prefix() + username - return RedirectPage(self.path).render() class ProfilePage(BaseRenderer): - def __init__(self, path, http_status_code): - super().__init__(path, http_status_code) - self.renderer = None + def __init__(self, path, http_status_code): + super().__init__(path, http_status_code) + self.renderer = None - def can_render(self): - if "." in self.path: - return False + def can_render(self): + if "." in self.path: + return False - # has prefix and path starts with prefix? - prefix = get_profile_url_prefix().lstrip("/") - if prefix and not self.path.startswith(prefix): - return False + # has prefix and path starts with prefix? + prefix = get_profile_url_prefix().lstrip("/") + if prefix and not self.path.startswith(prefix): + return False - # not a userpage? - username = self.get_username() - if RE_INVALID_USERNAME.search(username): - return False - # if there is prefix then we can allow all usernames - if prefix: - return True + # not a userpage? + username = self.get_username() + if RE_INVALID_USERNAME.search(username): + return False + # if there is prefix then we can allow all usernames + if prefix: + return True - # if we are having top-level usernames, then give preference to - # the existing website_route_rules, web pages, web forms etc. + # if we are having top-level usernames, then give preference to + # the existing website_route_rules, web pages, web forms etc. - # Don't handle any of the exsiting website_route_rules - routes = [rule['to_route'] for rule in frappe.get_hooks("website_route_rules")] - if self.path in routes: - return False + # Don't handle any of the exsiting website_route_rules + routes = [rule["to_route"] for rule in frappe.get_hooks("website_route_rules")] + if self.path in routes: + return False - # if any of the existing renders can render, let them do - renderers = [StaticPage, WebFormPage, DocumentPage, TemplatePage, ListPage, PrintPage] - for renderer in renderers: - renderer_instance = renderer(self.path, 200) - if renderer_instance.can_render(): - self.renderer = renderer_instance - return True + # if any of the existing renders can render, let them do + renderers = [StaticPage, WebFormPage, DocumentPage, TemplatePage, ListPage, PrintPage] + for renderer in renderers: + renderer_instance = renderer(self.path, 200) + if renderer_instance.can_render(): + self.renderer = renderer_instance + return True - return True + return True - def get_username(self): - prefix = get_profile_url_prefix().lstrip("/") - return self.path[len(prefix):] + def get_username(self): + prefix = get_profile_url_prefix().lstrip("/") + return self.path[len(prefix) :] + + def render(self): + if self.renderer: + return self.renderer.render() + else: + username = self.get_username() + return render_portal_page("profiles/profile", username=username) - def render(self): - if self.renderer: - return self.renderer.render() - else: - username = self.get_username() - return render_portal_page("profiles/profile", username=username) def render_portal_page(path, **kwargs): - frappe.form_dict.update(kwargs) - page = TemplatePage(path) - return page.render() + frappe.form_dict.update(kwargs) + page = TemplatePage(path) + return page.render() diff --git a/lms/patches/change_name_for_community_members.py b/lms/patches/change_name_for_community_members.py index 5d1b7f51..f17a69eb 100644 --- a/lms/patches/change_name_for_community_members.py +++ b/lms/patches/change_name_for_community_members.py @@ -1,12 +1,20 @@ -from __future__ import unicode_literals import frappe from frappe.model.naming import make_autoname from frappe.model.rename_doc import rename_doc + def execute(): frappe.reload_doc("community", "doctype", "community_member") docs = frappe.get_all("Community Member") for doc in docs: member = frappe.get_doc("Community Member", doc.name) name = make_autoname("hash", "Community Member") - rename_doc("Community Member", member.name, name, force=True, merge=False, ignore_permissions=True, ignore_if_exists=False) + rename_doc( + "Community Member", + member.name, + name, + force=True, + merge=False, + ignore_permissions=True, + ignore_if_exists=False, + ) diff --git a/lms/patches/create_mentor_request_email_templates.py b/lms/patches/create_mentor_request_email_templates.py index cfdc97af..006a86cf 100644 --- a/lms/patches/create_mentor_request_email_templates.py +++ b/lms/patches/create_mentor_request_email_templates.py @@ -1,31 +1,51 @@ -from __future__ import unicode_literals -import frappe, os +import os + +import frappe from frappe import _ + def execute(): - frappe.reload_doc("email", "doctype", "email_template") - base_path = frappe.get_app_path("lms", "templates", "emails") + frappe.reload_doc("email", "doctype", "email_template") + base_path = frappe.get_app_path("lms", "templates", "emails") - if not frappe.db.exists("Email Template", _('Mentor Request Creation Template')): - response = frappe.read_file(os.path.join(base_path, "mentor_request_creation_email.html")) - frappe.get_doc({ - 'doctype': 'Email Template', - 'name': _("Mentor Request Creation Template"), - 'response': response, - 'subject': _('Request for Mentorship'), - 'owner': frappe.session.user - }).insert(ignore_permissions=True) + if not frappe.db.exists("Email Template", _("Mentor Request Creation Template")): + response = frappe.read_file( + os.path.join(base_path, "mentor_request_creation_email.html") + ) + frappe.get_doc( + { + "doctype": "Email Template", + "name": _("Mentor Request Creation Template"), + "response": response, + "subject": _("Request for Mentorship"), + "owner": frappe.session.user, + } + ).insert(ignore_permissions=True) - frappe.db.set_value("LMS Settings", None, "mentor_request_creation", _('Mentor Request Creation Template')) + frappe.db.set_value( + "LMS Settings", + None, + "mentor_request_creation", + _("Mentor Request Creation Template"), + ) - if not frappe.db.exists("Email Template", _('Mentor Request Status Update Template')): - response = frappe.read_file(os.path.join(base_path, "mentor_request_status_update_email.html")) - frappe.get_doc({ - 'doctype': 'Email Template', - 'name': _("Mentor Request Status Update Template"), - 'response': response, - 'subject': _('The status of your application has changed.'), - 'owner': frappe.session.user - }).insert(ignore_permissions=True) + if not frappe.db.exists("Email Template", _("Mentor Request Status Update Template")): + response = frappe.read_file( + os.path.join(base_path, "mentor_request_status_update_email.html") + ) + frappe.get_doc( + { + "doctype": "Email Template", + "name": _("Mentor Request Status Update Template"), + "response": response, + "subject": _("The status of your application has changed."), + "owner": frappe.session.user, + } + ).insert(ignore_permissions=True) - frappe.db.set_value("LMS Settings", None, "mentor_request_status_update", _('Mentor Request Status Update Template')) + frappe.db.set_value( + "LMS Settings", + None, + "mentor_request_status_update", + _("Mentor Request Status Update Template"), + ) diff --git a/lms/patches/replace_member_with_user_in_batch_membership.py b/lms/patches/replace_member_with_user_in_batch_membership.py index 4c2ae2cc..56811a16 100644 --- a/lms/patches/replace_member_with_user_in_batch_membership.py +++ b/lms/patches/replace_member_with_user_in_batch_membership.py @@ -1,9 +1,9 @@ -from __future__ import unicode_literals import frappe + def execute(): - frappe.reload_doc("lms", "doctype", "lms_batch_membership") - memberships = frappe.get_all("LMS Batch Membership", ["member", "name"]) - for membership in memberships: - email = frappe.db.get_value("Community Member", membership.member, "email") - frappe.db.set_value("LMS Batch Membership", membership.name, "member", email) + frappe.reload_doc("lms", "doctype", "lms_batch_membership") + memberships = frappe.get_all("LMS Batch Membership", ["member", "name"]) + for membership in memberships: + email = frappe.db.get_value("Community Member", membership.member, "email") + frappe.db.set_value("LMS Batch Membership", membership.name, "member", email) diff --git a/lms/patches/replace_member_with_user_in_course_mentor_mapping.py b/lms/patches/replace_member_with_user_in_course_mentor_mapping.py index 1e868666..13a8b4f4 100644 --- a/lms/patches/replace_member_with_user_in_course_mentor_mapping.py +++ b/lms/patches/replace_member_with_user_in_course_mentor_mapping.py @@ -1,9 +1,9 @@ -from __future__ import unicode_literals import frappe + def execute(): - frappe.reload_doc("lms", "doctype", "lms_course_mentor_mapping") - mappings = frappe.get_all("LMS Course Mentor Mapping", ["mentor", "name"]) - for mapping in mappings: - email = frappe.db.get_value("Community Member", mapping.mentor, "email") - frappe.db.set_value("LMS Course Mentor Mapping", mapping.name, "mentor", email) + frappe.reload_doc("lms", "doctype", "lms_course_mentor_mapping") + mappings = frappe.get_all("LMS Course Mentor Mapping", ["mentor", "name"]) + for mapping in mappings: + email = frappe.db.get_value("Community Member", mapping.mentor, "email") + frappe.db.set_value("LMS Course Mentor Mapping", mapping.name, "mentor", email) diff --git a/lms/patches/replace_member_with_user_in_lms_message.py b/lms/patches/replace_member_with_user_in_lms_message.py index 8cf2cb2b..40e96860 100644 --- a/lms/patches/replace_member_with_user_in_lms_message.py +++ b/lms/patches/replace_member_with_user_in_lms_message.py @@ -1,10 +1,12 @@ -from __future__ import unicode_literals import frappe + def execute(): - frappe.reload_doc("lms", "doctype", "lms_message") - messages = frappe.get_all("LMS Message", ["author", "name"]) - for message in messages: - user = frappe.db.get_value("Community Member", message.author, ["email", "full_name"], as_dict=True) - frappe.db.set_value("LMS Message", message.name, "author", user.email) - frappe.db.set_value("LMS Message", message.name, "author_name", user.full_name) + frappe.reload_doc("lms", "doctype", "lms_message") + messages = frappe.get_all("LMS Message", ["author", "name"]) + for message in messages: + user = frappe.db.get_value( + "Community Member", message.author, ["email", "full_name"], as_dict=True + ) + frappe.db.set_value("LMS Message", message.name, "author", user.email) + frappe.db.set_value("LMS Message", message.name, "author_name", user.full_name) diff --git a/lms/patches/replace_member_with_user_in_mentor_request.py b/lms/patches/replace_member_with_user_in_mentor_request.py index abda27ae..a577f685 100644 --- a/lms/patches/replace_member_with_user_in_mentor_request.py +++ b/lms/patches/replace_member_with_user_in_mentor_request.py @@ -1,10 +1,12 @@ -from __future__ import unicode_literals import frappe + def execute(): - frappe.reload_doc("lms", "doctype", "lms_mentor_request") - requests = frappe.get_all("LMS Mentor Request", ["member", "name"]) - for request in requests: - user = frappe.db.get_value("Community Member", request.member, ["email", "full_name"], as_dict=True) - frappe.db.set_value("LMS Mentor Request", request.name, "member", user.email) - frappe.db.set_value("LMS Mentor Request", request.name, "member_name", user.full_name) + frappe.reload_doc("lms", "doctype", "lms_mentor_request") + requests = frappe.get_all("LMS Mentor Request", ["member", "name"]) + for request in requests: + user = frappe.db.get_value( + "Community Member", request.member, ["email", "full_name"], as_dict=True + ) + frappe.db.set_value("LMS Mentor Request", request.name, "member", user.email) + frappe.db.set_value("LMS Mentor Request", request.name, "member_name", user.full_name) diff --git a/lms/patches/save_abbr_for_community_members.py b/lms/patches/save_abbr_for_community_members.py index ccf3783d..a9224da8 100644 --- a/lms/patches/save_abbr_for_community_members.py +++ b/lms/patches/save_abbr_for_community_members.py @@ -1,11 +1,11 @@ -from __future__ import unicode_literals import frappe + def execute(): frappe.reload_doc("community", "doctype", "community_member") docs = frappe.get_all("Community Member") for doc in docs: member = frappe.get_doc("Community Member", doc.name) if not member.abbr: - abbr = ("").join([ s[0] for s in member.full_name.split() ]) + abbr = ("").join([s[0] for s in member.full_name.split()]) frappe.db.set_value("Community Member", member.name, "abbr", abbr) diff --git a/lms/patches/set_email_preferences.py b/lms/patches/set_email_preferences.py index e1bf8dde..4cc2b78c 100644 --- a/lms/patches/set_email_preferences.py +++ b/lms/patches/set_email_preferences.py @@ -1,9 +1,11 @@ -from __future__ import unicode_literals import frappe + def execute(): frappe.reload_doc("community", "doctype", "community_member") members = frappe.get_all("Community Member", ["name", "email_preference"]) for member in members: if not member.email_preference: - frappe.db.set_value("Community Member", member.name, "email_preference", "Email on every Message") \ No newline at end of file + frappe.db.set_value( + "Community Member", member.name, "email_preference", "Email on every Message" + ) diff --git a/lms/patches/v0_0/add_progress_to_membership.py b/lms/patches/v0_0/add_progress_to_membership.py index 868f63ee..d79c238d 100644 --- a/lms/patches/v0_0/add_progress_to_membership.py +++ b/lms/patches/v0_0/add_progress_to_membership.py @@ -1,22 +1,23 @@ import frappe from frappe.utils import rounded + from lms.lms.utils import get_course_progress + def execute(): - frappe.reload_doc("lms", "doctype", "lms_batch_membership") - memberships = frappe.get_all( - "LMS Batch Membership", - ["name", "course", "member"], - order_by="course") + frappe.reload_doc("lms", "doctype", "lms_batch_membership") + memberships = frappe.get_all( + "LMS Batch Membership", ["name", "course", "member"], order_by="course" + ) - if len(memberships): - current_course = memberships[0].course - for membership in memberships: - if current_course != membership.course: - current_course = membership.course + if len(memberships): + current_course = memberships[0].course + for membership in memberships: + if current_course != membership.course: + current_course = membership.course - progress = rounded(get_course_progress(current_course, membership.member)) - frappe.db.set_value("LMS Batch Membership", membership.name, "progress", progress) + progress = rounded(get_course_progress(current_course, membership.member)) + frappe.db.set_value("LMS Batch Membership", membership.name, "progress", progress) - frappe.db.delete("Prepared Report", {"ref_report_doctype": "Course Progress Summary"}) - frappe.db.set_value("Report", "Course Progress Summary", "prepared_report", 0) + frappe.db.delete("Prepared Report", {"ref_report_doctype": "Course Progress Summary"}) + frappe.db.set_value("Report", "Course Progress Summary", "prepared_report", 0) diff --git a/lms/patches/v0_0/certification_member_field_data.py b/lms/patches/v0_0/certification_member_field_data.py index c1015703..61dac00e 100644 --- a/lms/patches/v0_0/certification_member_field_data.py +++ b/lms/patches/v0_0/certification_member_field_data.py @@ -1,7 +1,10 @@ import frappe + def execute(): - frappe.reload_doc("lms", "doctype", "lms_certification") - certificates = frappe.get_all("LMS Certification", fields=["name", "student"]) - for certificate in certificates: - frappe.db.set_value("LMS Certification", certificate.name, "member", certificate.student) + frappe.reload_doc("lms", "doctype", "lms_certification") + certificates = frappe.get_all("LMS Certification", fields=["name", "student"]) + for certificate in certificates: + frappe.db.set_value( + "LMS Certification", certificate.name, "member", certificate.student + ) diff --git a/lms/patches/v0_0/change_published_field_data.py b/lms/patches/v0_0/change_published_field_data.py index 52089fee..82aee4ca 100644 --- a/lms/patches/v0_0/change_published_field_data.py +++ b/lms/patches/v0_0/change_published_field_data.py @@ -1,7 +1,8 @@ import frappe + def execute(): - frappe.reload_doc("lms", "doctype", "lms_course") - courses = frappe.get_all("LMS Course", fields=["name", "is_published"]) - for course in courses: - frappe.db.set_value("LMS Course", course.name, "published", course.is_published) + frappe.reload_doc("lms", "doctype", "lms_course") + courses = frappe.get_all("LMS Course", fields=["name", "is_published"]) + for course in courses: + frappe.db.set_value("LMS Course", course.name, "published", course.is_published) diff --git a/lms/patches/v0_0/chapter_lesson_index_table.py b/lms/patches/v0_0/chapter_lesson_index_table.py index cbb489fb..4b55b41e 100644 --- a/lms/patches/v0_0/chapter_lesson_index_table.py +++ b/lms/patches/v0_0/chapter_lesson_index_table.py @@ -1,50 +1,34 @@ -from __future__ import unicode_literals import frappe -def execute(): - frappe.reload_doc("lms", "doctype", "lms_course") - frappe.reload_doc("lms", "doctype", "chapter") - frappe.reload_doc("lms", "doctype", "lesson") - frappe.reload_doc("lms", "doctype", "lessons") - frappe.reload_doc("lms", "doctype", "chapters") - update_chapters() - update_lessons() +def execute(): + frappe.reload_doc("lms", "doctype", "lms_course") + frappe.reload_doc("lms", "doctype", "chapter") + frappe.reload_doc("lms", "doctype", "lesson") + frappe.reload_doc("lms", "doctype", "lessons") + frappe.reload_doc("lms", "doctype", "chapters") + + update_chapters() + update_lessons() + def update_chapters(): - courses = frappe.get_all("LMS Course", pluck="name") - for course in courses: - course_details = frappe.get_doc("LMS Course", course) - chapters = frappe.get_all("Chapter", - { - "course": course - }, - ["name"], - order_by= "index_" - ) - for chapter in chapters: - course_details.append("chapters", - { - "chapter": chapter.name - }) + courses = frappe.get_all("LMS Course", pluck="name") + for course in courses: + course_details = frappe.get_doc("LMS Course", course) + chapters = frappe.get_all("Chapter", {"course": course}, ["name"], order_by="index_") + for chapter in chapters: + course_details.append("chapters", {"chapter": chapter.name}) + + course_details.save() - course_details.save() def update_lessons(): - chapters = frappe.get_all("Chapter", pluck="name") - for chapter in chapters: - chapter_details = frappe.get_doc("Chapter", chapter) - lessons = frappe.get_all("Lesson", - { - "chapter": chapter - }, - ["name"], - order_by= "index_" - ) - for lesson in lessons: - chapter_details.append("lessons", - { - "lesson": lesson.name - }) + chapters = frappe.get_all("Chapter", pluck="name") + for chapter in chapters: + chapter_details = frappe.get_doc("Chapter", chapter) + lessons = frappe.get_all("Lesson", {"chapter": chapter}, ["name"], order_by="index_") + for lesson in lessons: + chapter_details.append("lessons", {"lesson": lesson.name}) - chapter_details.save() + chapter_details.save() diff --git a/lms/patches/v0_0/course_instructor_update.py b/lms/patches/v0_0/course_instructor_update.py index f8fd6019..f9536e83 100644 --- a/lms/patches/v0_0/course_instructor_update.py +++ b/lms/patches/v0_0/course_instructor_update.py @@ -1,7 +1,8 @@ import frappe + def execute(): - frappe.reload_doc("lms", "doctype", "lms_course") - courses = frappe.get_all("LMS Course", fields=["name", "owner"]) - for course in courses: - frappe.db.set_value("LMS Course", course.name, "instructor", course.owner) + frappe.reload_doc("lms", "doctype", "lms_course") + courses = frappe.get_all("LMS Course", fields=["name", "owner"]) + for course in courses: + frappe.db.set_value("LMS Course", course.name, "instructor", course.owner) diff --git a/lms/patches/v0_0/create_course_instructor_role.py b/lms/patches/v0_0/create_course_instructor_role.py index 6ad39808..9d3299b9 100644 --- a/lms/patches/v0_0/create_course_instructor_role.py +++ b/lms/patches/v0_0/create_course_instructor_role.py @@ -1,6 +1,9 @@ from venv import create + import frappe + from lms.install import create_instructor_role + def execute(): - create_instructor_role() + create_instructor_role() diff --git a/lms/patches/v0_0/create_course_moderator_role.py b/lms/patches/v0_0/create_course_moderator_role.py index 49b2cab4..e14c6e27 100644 --- a/lms/patches/v0_0/create_course_moderator_role.py +++ b/lms/patches/v0_0/create_course_moderator_role.py @@ -1,7 +1,9 @@ from venv import create + import frappe + from lms.install import create_moderator_role -def execute(): - create_moderator_role() +def execute(): + create_moderator_role() diff --git a/lms/patches/v0_0/delete_course_web_forms.py b/lms/patches/v0_0/delete_course_web_forms.py index bd72edea..aeea4c76 100644 --- a/lms/patches/v0_0/delete_course_web_forms.py +++ b/lms/patches/v0_0/delete_course_web_forms.py @@ -1,6 +1,7 @@ import frappe + def execute(): - frappe.db.delete("Web Form", "lesson") - frappe.db.delete("Web Form", "chapter") - frappe.db.delete("Web Form", "course") + frappe.db.delete("Web Form", "lesson") + frappe.db.delete("Web Form", "chapter") + frappe.db.delete("Web Form", "course") diff --git a/lms/patches/v0_0/delete_old_module_docs.py b/lms/patches/v0_0/delete_old_module_docs.py index 861aa50d..c1a873d6 100644 --- a/lms/patches/v0_0/delete_old_module_docs.py +++ b/lms/patches/v0_0/delete_old_module_docs.py @@ -1,15 +1,16 @@ import frappe + def execute(): - frappe.db.delete("DocType", {"module": "Conference"}) - frappe.db.delete("DocType", {"module": "Hackathon"}) - frappe.db.delete("DocType", {"module": "Event Management"}) + frappe.db.delete("DocType", {"module": "Conference"}) + frappe.db.delete("DocType", {"module": "Hackathon"}) + frappe.db.delete("DocType", {"module": "Event Management"}) - frappe.db.delete("Web Form", {"module": "Conference"}) - frappe.db.delete("Web Form", {"module": "Hackathon"}) - frappe.db.delete("Web Form", {"module": "Event Management"}) + frappe.db.delete("Web Form", {"module": "Conference"}) + frappe.db.delete("Web Form", {"module": "Hackathon"}) + frappe.db.delete("Web Form", {"module": "Event Management"}) - frappe.db.delete("Module Def", "Conference") - frappe.db.delete("Module Def", "Hackathon") - frappe.db.delete("Module Def", "Event Management") + frappe.db.delete("Module Def", "Conference") + frappe.db.delete("Module Def", "Hackathon") + frappe.db.delete("Module Def", "Event Management") diff --git a/lms/patches/v0_0/modify_installed_apps_list.py b/lms/patches/v0_0/modify_installed_apps_list.py index da673e90..3a508e7b 100644 --- a/lms/patches/v0_0/modify_installed_apps_list.py +++ b/lms/patches/v0_0/modify_installed_apps_list.py @@ -1,11 +1,9 @@ -from __future__ import unicode_literals import frappe from frappe.installer import add_to_installed_apps, remove_from_installed_apps + def execute(): - if "community" in frappe.db.get_global("installed_apps"): - remove_from_installed_apps("community") - add_to_installed_apps("school") - - + if "community" in frappe.db.get_global("installed_apps"): + remove_from_installed_apps("community") + add_to_installed_apps("school") diff --git a/lms/patches/v0_0/move_certification_to_certificate.py b/lms/patches/v0_0/move_certification_to_certificate.py index 1005ace5..d47af250 100644 --- a/lms/patches/v0_0/move_certification_to_certificate.py +++ b/lms/patches/v0_0/move_certification_to_certificate.py @@ -1,14 +1,19 @@ import frappe + def execute(): - frappe.reload_doc("lms", "doctype", "lms_certification") - frappe.reload_doc("lms", "doctype", "lms_certificate") - old = frappe.get_all("LMS Certification", fields=["name", "course", "student", "issue_date", "expiry_date"]) - for data in old: - frappe.get_doc({ - "doctype": "LMS Certificate", - "course": data.course, - "member": data.student, - "issue_date": data.issue_date, - "expiry_date": data.expiry_date - }).insert(ignore_permissions=True, ignore_mandatory=True) + frappe.reload_doc("lms", "doctype", "lms_certification") + frappe.reload_doc("lms", "doctype", "lms_certificate") + old = frappe.get_all( + "LMS Certification", fields=["name", "course", "student", "issue_date", "expiry_date"] + ) + for data in old: + frappe.get_doc( + { + "doctype": "LMS Certificate", + "course": data.course, + "member": data.student, + "issue_date": data.issue_date, + "expiry_date": data.expiry_date, + } + ).insert(ignore_permissions=True, ignore_mandatory=True) diff --git a/lms/patches/v0_0/multiple_instructors.py b/lms/patches/v0_0/multiple_instructors.py index b0e8a59a..14442454 100644 --- a/lms/patches/v0_0/multiple_instructors.py +++ b/lms/patches/v0_0/multiple_instructors.py @@ -1,15 +1,18 @@ import frappe + def execute(): - frappe.reload_doc("lms", "doctype", "lms_course") - frappe.reload_doc("lms", "doctype", "course_instructor") - courses = frappe.get_all("LMS Course", fields=["name", "instructor"]) - for course in courses: - doc = frappe.get_doc({ - "doctype": "Course Instructor", - "parent": course.name, - "parentfield": "instructors", - "parenttype": "LMS Course", - "instructor": course.instructor - }) - doc.save() + frappe.reload_doc("lms", "doctype", "lms_course") + frappe.reload_doc("lms", "doctype", "course_instructor") + courses = frappe.get_all("LMS Course", fields=["name", "instructor"]) + for course in courses: + doc = frappe.get_doc( + { + "doctype": "Course Instructor", + "parent": course.name, + "parentfield": "instructors", + "parenttype": "LMS Course", + "instructor": course.instructor, + } + ) + doc.save() diff --git a/lms/patches/v0_0/quiz_submission_member.py b/lms/patches/v0_0/quiz_submission_member.py index 02d3349b..fc532275 100644 --- a/lms/patches/v0_0/quiz_submission_member.py +++ b/lms/patches/v0_0/quiz_submission_member.py @@ -1,8 +1,11 @@ import frappe -def execute(): - frappe.reload_doc("lms", "doctype", "lms_quiz_submission") - submissions = frappe.db.get_all("LMS Quiz Submission", fields=["name", "owner"]) - for submission in submissions: - frappe.db.set_value("LMS Quiz Submission", submission.name, "member", submission.owner) +def execute(): + frappe.reload_doc("lms", "doctype", "lms_quiz_submission") + submissions = frappe.db.get_all("LMS Quiz Submission", fields=["name", "owner"]) + + for submission in submissions: + frappe.db.set_value( + "LMS Quiz Submission", submission.name, "member", submission.owner + ) diff --git a/lms/patches/v0_0/rename_chapter_and_lesson_doctype.py b/lms/patches/v0_0/rename_chapter_and_lesson_doctype.py index 119e1e4f..825b53ad 100644 --- a/lms/patches/v0_0/rename_chapter_and_lesson_doctype.py +++ b/lms/patches/v0_0/rename_chapter_and_lesson_doctype.py @@ -1,48 +1,54 @@ import frappe + def execute(): - frappe.reload_doc("lms", "doctype", "course_chapter") - frappe.reload_doc("lms", "doctype", "course_lesson") - frappe.reload_doc("lms", "doctype", "chapter_reference") - frappe.reload_doc("lms", "doctype", "lesson_reference") - frappe.reload_doc("lms", "doctype", "exercise") - frappe.reload_doc("lms", "doctype", "exercise_submission") - frappe.reload_doc("lms", "doctype", "lms_batch_membership") - frappe.reload_doc("lms", "doctype", "lms_course") - frappe.reload_doc("lms", "doctype", "lms_course_progress") - frappe.reload_doc("lms", "doctype", "lms_quiz") + frappe.reload_doc("lms", "doctype", "course_chapter") + frappe.reload_doc("lms", "doctype", "course_lesson") + frappe.reload_doc("lms", "doctype", "chapter_reference") + frappe.reload_doc("lms", "doctype", "lesson_reference") + frappe.reload_doc("lms", "doctype", "exercise") + frappe.reload_doc("lms", "doctype", "exercise_submission") + frappe.reload_doc("lms", "doctype", "lms_batch_membership") + frappe.reload_doc("lms", "doctype", "lms_course") + frappe.reload_doc("lms", "doctype", "lms_course_progress") + frappe.reload_doc("lms", "doctype", "lms_quiz") - if not frappe.db.count("Course Chapter"): - move_chapters() + if not frappe.db.count("Course Chapter"): + move_chapters() - if not frappe.db.count("Course Lesson"): - move_lessons() + if not frappe.db.count("Course Lesson"): + move_lessons() + + change_parent_for_lesson_reference() - change_parent_for_lesson_reference() def move_chapters(): - docs = frappe.get_all("Chapter", fields=["*"]) - for doc in docs: - if frappe.db.exists("LMS Course", doc.course): - name = doc.name - doc.update({"doctype": "Course Chapter"}) - del doc["name"] - new_doc = frappe.get_doc(doc) - new_doc.save() - frappe.rename_doc("Course Chapter", new_doc.name, name) + docs = frappe.get_all("Chapter", fields=["*"]) + for doc in docs: + if frappe.db.exists("LMS Course", doc.course): + name = doc.name + doc.update({"doctype": "Course Chapter"}) + del doc["name"] + new_doc = frappe.get_doc(doc) + new_doc.save() + frappe.rename_doc("Course Chapter", new_doc.name, name) + def move_lessons(): - docs = frappe.get_all("Lesson", fields=["*"]) - for doc in docs: - if frappe.db.exists("Chapter", doc.chapter): - name = doc.name - doc.update({"doctype": "Course Lesson"}) - del doc["name"] - new_doc = frappe.get_doc(doc) - new_doc.save() - frappe.rename_doc("Course Lesson", new_doc.name, name) + docs = frappe.get_all("Lesson", fields=["*"]) + for doc in docs: + if frappe.db.exists("Chapter", doc.chapter): + name = doc.name + doc.update({"doctype": "Course Lesson"}) + del doc["name"] + new_doc = frappe.get_doc(doc) + new_doc.save() + frappe.rename_doc("Course Lesson", new_doc.name, name) + def change_parent_for_lesson_reference(): - lesson_reference = frappe.get_all("Lesson Reference", fields=["name", "parent"]) - for reference in lesson_reference: - frappe.db.set_value("Lesson Reference", reference.name, "parenttype", "Course Chapter") + lesson_reference = frappe.get_all("Lesson Reference", fields=["name", "parent"]) + for reference in lesson_reference: + frappe.db.set_value( + "Lesson Reference", reference.name, "parenttype", "Course Chapter" + ) diff --git a/lms/patches/v0_0/rename_chapters_and_lessons_doctype.py b/lms/patches/v0_0/rename_chapters_and_lessons_doctype.py index 82d45556..653ab990 100644 --- a/lms/patches/v0_0/rename_chapters_and_lessons_doctype.py +++ b/lms/patches/v0_0/rename_chapters_and_lessons_doctype.py @@ -1,32 +1,33 @@ import frappe + def execute(): - frappe.reload_doc("lms", "doctype", "lms_course") - frappe.reload_doc("lms", "doctype", "chapter") - frappe.reload_doc("lms", "doctype", "lesson") - frappe.reload_doc("lms", "doctype", "chapter_reference") - frappe.reload_doc("lms", "doctype", "lesson_reference") + frappe.reload_doc("lms", "doctype", "lms_course") + frappe.reload_doc("lms", "doctype", "chapter") + frappe.reload_doc("lms", "doctype", "lesson") + frappe.reload_doc("lms", "doctype", "chapter_reference") + frappe.reload_doc("lms", "doctype", "lesson_reference") - if not frappe.db.count("Chapter Reference"): - move_chapters() + if not frappe.db.count("Chapter Reference"): + move_chapters() + + if not frappe.db.count("Lesson Reference"): + move_lessons() - if not frappe.db.count("Lesson Reference"): - move_lessons() def move_chapters(): - docs = frappe.get_all("Chapters", fields=["*"]) - for doc in docs: - keys = doc - keys.update({"doctype": "Chapter Reference"}) - del keys["name"] - frappe.get_doc(keys).save() + docs = frappe.get_all("Chapters", fields=["*"]) + for doc in docs: + keys = doc + keys.update({"doctype": "Chapter Reference"}) + del keys["name"] + frappe.get_doc(keys).save() + def move_lessons(): - docs = frappe.get_all("Lessons", fields=["*"]) - for doc in docs: - keys = doc - keys.update({"doctype": "Lesson Reference"}) - del keys["name"] - frappe.get_doc(keys).save() - - + docs = frappe.get_all("Lessons", fields=["*"]) + for doc in docs: + keys = doc + keys.update({"doctype": "Lesson Reference"}) + del keys["name"] + frappe.get_doc(keys).save() diff --git a/lms/patches/v0_0/rename_school_to_lms.py b/lms/patches/v0_0/rename_school_to_lms.py index a8fce72b..3ca20d41 100644 --- a/lms/patches/v0_0/rename_school_to_lms.py +++ b/lms/patches/v0_0/rename_school_to_lms.py @@ -1,11 +1,9 @@ -from __future__ import unicode_literals import frappe from frappe.installer import add_to_installed_apps, remove_from_installed_apps + def execute(): - if "school" in frappe.db.get_global("installed_apps"): - remove_from_installed_apps("school") - add_to_installed_apps("lms") - - + if "school" in frappe.db.get_global("installed_apps"): + remove_from_installed_apps("school") + add_to_installed_apps("lms") diff --git a/lms/patches/v0_0/set_course_in_lesson.py b/lms/patches/v0_0/set_course_in_lesson.py index 7d0ccd1f..a46b9d44 100644 --- a/lms/patches/v0_0/set_course_in_lesson.py +++ b/lms/patches/v0_0/set_course_in_lesson.py @@ -1,8 +1,9 @@ import frappe + def execute(): - frappe.reload_doc("lms", "doctype", "course_lesson") - lessons = frappe.get_all("Course Lesson", fields=["name", "chapter"]) - for lesson in lessons: - course = frappe.db.get_value("Course Chapter", lesson.chapter, "course") - frappe.db.set_value("Course Lesson", lesson.name, "course", course) + frappe.reload_doc("lms", "doctype", "course_lesson") + lessons = frappe.get_all("Course Lesson", fields=["name", "chapter"]) + for lesson in lessons: + course = frappe.db.get_value("Course Chapter", lesson.chapter, "course") + frappe.db.set_value("Course Lesson", lesson.name, "course", course) diff --git a/lms/patches/v0_0/set_courses_page_as_home.py b/lms/patches/v0_0/set_courses_page_as_home.py index 32cb38db..2cb41408 100644 --- a/lms/patches/v0_0/set_courses_page_as_home.py +++ b/lms/patches/v0_0/set_courses_page_as_home.py @@ -1,6 +1,7 @@ import frappe + def execute(): - frappe.db.set_value("Portal Settings", None, "default_portal_home", "/courses") - frappe.db.set_value("Role", "Course Instructor", "home_page", "") - frappe.db.set_value("Role", "Course Moderator", "home_page", "") + frappe.db.set_value("Portal Settings", None, "default_portal_home", "/courses") + frappe.db.set_value("Role", "Course Instructor", "home_page", "") + frappe.db.set_value("Role", "Course Moderator", "home_page", "") diff --git a/lms/patches/v0_0/set_dashboard.py b/lms/patches/v0_0/set_dashboard.py index 9c24c4e5..fc00ed3b 100644 --- a/lms/patches/v0_0/set_dashboard.py +++ b/lms/patches/v0_0/set_dashboard.py @@ -1,4 +1,5 @@ import frappe + def execute(): - frappe.db.set_value("Portal Settings", None, "default_portal_home", "/users") + frappe.db.set_value("Portal Settings", None, "default_portal_home", "/users") diff --git a/lms/patches/v0_0/set_status_in_course.py b/lms/patches/v0_0/set_status_in_course.py index abf24a1b..e084e825 100644 --- a/lms/patches/v0_0/set_status_in_course.py +++ b/lms/patches/v0_0/set_status_in_course.py @@ -1,8 +1,11 @@ import frappe + def execute(): - frappe.reload_doc("lms", "doctype", "lms_course") - courses = frappe.get_all("LMS Course", {"status": ("is", "not set")}, ["name", "published"]) - for course in courses: - status = "Approved" if course.published else "In Progress" - frappe.db.set_value("LMS Course", course.name, "status", status) + frappe.reload_doc("lms", "doctype", "lms_course") + courses = frappe.get_all( + "LMS Course", {"status": ("is", "not set")}, ["name", "published"] + ) + for course in courses: + status = "Approved" if course.published else "In Progress" + frappe.db.set_value("LMS Course", course.name, "status", status) diff --git a/lms/plugins.py b/lms/plugins.py index 7d9972be..0c9bdb63 100644 --- a/lms/plugins.py +++ b/lms/plugins.py @@ -16,121 +16,116 @@ be loaded in a webpage. import frappe + class PageExtension: - """PageExtension is a plugin to inject custom styles and scripts - into a web page. + """PageExtension is a plugin to inject custom styles and scripts + into a web page. - The subclasses should overwrite the `render_header()` and - `render_footer()` methods to inject whatever styles/scripts into - the webpage. - """ - def __init__(self): - self.context = frappe._dict() + The subclasses should overwrite the `render_header()` and + `render_footer()` methods to inject whatever styles/scripts into + the webpage. + """ - def set_context(self, context): - self.context = context + def __init__(self): + self.context = frappe._dict() - def render_header(self): - """Returns the HTML snippet to be included in the head section - of the web page. + def set_context(self, context): + self.context = context - Typically used to include the stylesheets and javascripts to be - included in the of the webpage. - """ - return "" + def render_header(self): + """Returns the HTML snippet to be included in the head section + of the web page. - def render_footer(self): - """Returns the HTML snippet to be included in the body tag at - the end of web page. + Typically used to include the stylesheets and javascripts to be + included in the of the webpage. + """ + return "" + + def render_footer(self): + """Returns the HTML snippet to be included in the body tag at + the end of web page. + + Typically used to include javascripts that need to be executed + after the page is loaded. + """ + return "" - Typically used to include javascripts that need to be executed - after the page is loaded. - """ - return "" class ProfileTab: - """Base class for profile tabs. + """Base class for profile tabs. - Every subclass of ProfileTab must implement two methods: - - get_title() - - render() - """ - def __init__(self, user): - self.user = user + Every subclass of ProfileTab must implement two methods: + - get_title() + - render() + """ - def get_title(self): - """Returns the title of the tab. + def __init__(self, user): + self.user = user - Every subclass must implement this. - """ - raise NotImplementedError() + def get_title(self): + """Returns the title of the tab. - def render(self): - """Renders the contents of the tab as HTML. + Every subclass must implement this. + """ + raise NotImplementedError() + + def render(self): + """Renders the contents of the tab as HTML. + + Every subclass must implement this. + """ + raise NotImplementedError() - Every subclass must implement this. - """ - raise NotImplementedError() class LiveCodeExtension(PageExtension): - def render_header(self): - livecode_url = frappe.get_value("LMS Settings", None, "livecode_url") - context = { - "livecode_url": livecode_url - } - return frappe.render_template( - "templates/livecode/extension_header.html", - context) + def render_header(self): + livecode_url = frappe.get_value("LMS Settings", None, "livecode_url") + context = {"livecode_url": livecode_url} + return frappe.render_template("templates/livecode/extension_header.html", context) + + def render_footer(self): + livecode_url = frappe.get_value("LMS Settings", None, "livecode_url") + context = {"livecode_url": livecode_url} + return frappe.render_template("templates/livecode/extension_footer.html", context) - def render_footer(self): - livecode_url = frappe.get_value("LMS Settings", None, "livecode_url") - context = { - "livecode_url": livecode_url - } - return frappe.render_template( - "templates/livecode/extension_footer.html", - context) def set_mandatory_fields_for_profile(): - profile_form = frappe.get_doc("Web Form", "profile") - profile_mandatory_fields = frappe.get_hooks("profile_mandatory_fields") - for field in profile_form.web_form_fields: - field.reqd = 0 - if field.fieldname in profile_mandatory_fields: - field.reqd = 1 + profile_form = frappe.get_doc("Web Form", "profile") + profile_mandatory_fields = frappe.get_hooks("profile_mandatory_fields") + for field in profile_form.web_form_fields: + field.reqd = 0 + if field.fieldname in profile_mandatory_fields: + field.reqd = 1 + + profile_form.save() - profile_form.save() def quiz_renderer(quiz_name): - quiz = frappe.get_doc("LMS Quiz", quiz_name) + quiz = frappe.get_doc("LMS Quiz", quiz_name) - context = { - "quiz": quiz - } + context = {"quiz": quiz} - no_of_attempts = frappe.db.count("LMS Quiz Submission", { - "owner": frappe.session.user, - "quiz": quiz_name}) + no_of_attempts = frappe.db.count( + "LMS Quiz Submission", {"owner": frappe.session.user, "quiz": quiz_name} + ) - if quiz.max_attempts and no_of_attempts >= quiz.max_attempts: - last_attempt_score = frappe.db.get_value("LMS Quiz Submission", { - "owner": frappe.session.user, - "quiz": quiz_name - }, ["score"]) + if quiz.max_attempts and no_of_attempts >= quiz.max_attempts: + last_attempt_score = frappe.db.get_value( + "LMS Quiz Submission", {"owner": frappe.session.user, "quiz": quiz_name}, ["score"] + ) + + context.update({"attempts_exceeded": True, "last_attempt_score": last_attempt_score}) + return frappe.render_template("templates/quiz.html", context) - context.update({ - "attempts_exceeded": True, - "last_attempt_score": last_attempt_score - }) - return frappe.render_template("templates/quiz.html", context) def exercise_renderer(argument): - exercise = frappe.get_doc("Exercise", argument) - context = dict(exercise=exercise) - return frappe.render_template("templates/exercise.html", context) + exercise = frappe.get_doc("Exercise", argument) + context = dict(exercise=exercise) + return frappe.render_template("templates/exercise.html", context) + def youtube_video_renderer(video_id): - return f""" + return f"""