Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f01e634445 |
46
.github/helper/install.sh
vendored
46
.github/helper/install.sh
vendored
@@ -1,46 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
cd ~ || exit
|
||||
|
||||
echo "Setting Up Bench..."
|
||||
|
||||
pip install frappe-bench
|
||||
bench -v init frappe-bench --skip-assets --python "$(which python)"
|
||||
cd ./frappe-bench || exit
|
||||
|
||||
bench -v setup requirements
|
||||
|
||||
echo "Setting Up LMS App..."
|
||||
bench get-app lms "${GITHUB_WORKSPACE}"
|
||||
|
||||
echo "Setting Up Sites & Database..."
|
||||
|
||||
mkdir ~/frappe-bench/sites/lms.test
|
||||
cp "${GITHUB_WORKSPACE}/.github/helper/site_config.json" ~/frappe-bench/sites/lms.test/site_config.json
|
||||
|
||||
|
||||
mariadb --host 127.0.0.1 --port 3306 -u root -p123 -e "SET GLOBAL character_set_server = 'utf8mb4'";
|
||||
mariadb --host 127.0.0.1 --port 3306 -u root -p123 -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'";
|
||||
|
||||
mariadb --host 127.0.0.1 --port 3306 -u root -p123 -e "CREATE DATABASE test_lms";
|
||||
mariadb --host 127.0.0.1 --port 3306 -u root -p123 -e "CREATE USER 'test_lms'@'localhost' IDENTIFIED BY 'test_lms'";
|
||||
mariadb --host 127.0.0.1 --port 3306 -u root -p123 -e "GRANT ALL PRIVILEGES ON \`test_lms\`.* TO 'test_lms'@'localhost'";
|
||||
|
||||
mariadb --host 127.0.0.1 --port 3306 -u root -p123 -e "FLUSH PRIVILEGES";
|
||||
|
||||
echo "Setting Up Procfile..."
|
||||
|
||||
sed -i 's/^watch:/# watch:/g' Procfile
|
||||
sed -i 's/^schedule:/# schedule:/g' Procfile
|
||||
|
||||
echo "Starting Bench..."
|
||||
|
||||
bench start &> bench_start.log &
|
||||
|
||||
CI=Yes bench build &
|
||||
build_pid=$!
|
||||
|
||||
bench --site lms.test reinstall --yes
|
||||
bench --site lms.test install-app lms
|
||||
|
||||
wait $build_pid
|
||||
13
.github/helper/install_dependencies.sh
vendored
13
.github/helper/install_dependencies.sh
vendored
@@ -1,13 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "Setting Up System Dependencies..."
|
||||
|
||||
sudo apt update
|
||||
sudo apt install libcups2-dev redis-server mariadb-client-10.6
|
||||
|
||||
install_wkhtmltopdf() {
|
||||
wget -q https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.focal_amd64.deb
|
||||
sudo apt install ./wkhtmltox_0.12.6-1.focal_amd64.deb
|
||||
}
|
||||
install_wkhtmltopdf &
|
||||
20
.github/helper/site_config.json
vendored
20
.github/helper/site_config.json
vendored
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"db_host": "127.0.0.1",
|
||||
"db_port": 3306,
|
||||
"db_name": "test_lms",
|
||||
"db_password": "test_lms",
|
||||
"allow_tests": true,
|
||||
"enable_ui_tests": true,
|
||||
"db_type": "mariadb",
|
||||
"auto_email_id": "test@example.com",
|
||||
"mail_server": "smtp.example.com",
|
||||
"mail_login": "test@example.com",
|
||||
"mail_password": "test",
|
||||
"admin_password": "admin",
|
||||
"root_login": "root",
|
||||
"root_password": "123",
|
||||
"host_name": "http://lms.test:8000",
|
||||
"monitor": 1,
|
||||
"server_script_enabled": true,
|
||||
"mute_emails": true
|
||||
}
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Server Tests
|
||||
name: Run tests
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
|
||||
116
.github/workflows/ui-tests.yml
vendored
116
.github/workflows/ui-tests.yml
vendored
@@ -1,116 +0,0 @@
|
||||
name: UI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ main ]
|
||||
|
||||
permissions:
|
||||
# Do not change this as GITHUB_TOKEN is being used by roulette
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository_owner == 'frappe' }}
|
||||
timeout-minutes: 60
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
name: UI Tests (Cypress)
|
||||
|
||||
services:
|
||||
mariadb:
|
||||
image: mariadb:10.6
|
||||
env:
|
||||
MARIADB_ROOT_PASSWORD: 123
|
||||
ports:
|
||||
- 3306:3306
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Check for valid Python & Merge Conflicts
|
||||
run: |
|
||||
python -m compileall -q -f "${GITHUB_WORKSPACE}"
|
||||
if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
|
||||
then echo "Found merge conflicts"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
check-latest: true
|
||||
|
||||
- name: Add to Hosts
|
||||
run: |
|
||||
echo "127.0.0.1 lms.test" | sudo tee -a /etc/hosts
|
||||
|
||||
- name: Cache pip
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
${{ runner.os }}-
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v3
|
||||
id: yarn-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-ui-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-ui-
|
||||
|
||||
- name: Cache cypress binary
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.cache/Cypress
|
||||
key: ${{ runner.os }}-cypress
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh
|
||||
bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
|
||||
env:
|
||||
BEFORE: ${{ env.GITHUB_EVENT_PATH.before }}
|
||||
AFTER: ${{ env.GITHUB_EVENT_PATH.after }}
|
||||
TYPE: ui
|
||||
DB: mariadb
|
||||
|
||||
- name: Site Setup
|
||||
run: |
|
||||
cd ~/frappe-bench/
|
||||
bench --site lms.test execute frappe.utils.install.complete_setup_wizard
|
||||
bench --site lms.test execute frappe.tests.ui_test_helpers.create_test_user
|
||||
|
||||
- name: UI Tests
|
||||
run: cd ~/frappe-bench/ && bench --site lms.test run-ui-tests lms --headless
|
||||
env:
|
||||
CYPRESS_BASE_URL: http://lms.test:8000
|
||||
CYPRESS_RECORD_KEY: 095366ec-7b9f-41bd-aeec-03bb76d627fe
|
||||
|
||||
- name: Stop server and wait for coverage file
|
||||
run: |
|
||||
ps -ef | grep "[f]rappe serve" | awk '{print $2}' | xargs kill -s SIGINT
|
||||
sleep 5
|
||||
|
||||
- name: Show bench output
|
||||
if: ${{ always() }}
|
||||
run: cat ~/frappe-bench/bench_start.log || true
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -8,5 +8,3 @@ lms/public/dist
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
node_modules
|
||||
package-lock.json
|
||||
@@ -7,9 +7,11 @@ repos:
|
||||
rev: v4.3.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
files: "lms.*"
|
||||
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
|
||||
|
||||
@@ -52,7 +52,7 @@ These are some of the tools it's built on:
|
||||
You need Docker, docker-compose, and git setup on your machine. Refer to [Docker documentation](https://docs.docker.com/). After that, run the following commands:
|
||||
```
|
||||
git clone https://github.com/frappe/lms
|
||||
cd apps/lms/docker
|
||||
cd lms/docker
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
@@ -71,7 +71,7 @@ Currently, this app depends on the `develop` branch of [frappe](https://github.c
|
||||
bench --site lms.test install-app lms
|
||||
bench --site lms.test add-to-hosts
|
||||
|
||||
1. Now, you can access the site at `http://lms.test:8000`
|
||||
1. Now, you can access the site at `http://gameplan.test:8080`
|
||||
|
||||
|
||||
## Deployment
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
const { defineConfig } = require("cypress");
|
||||
|
||||
module.exports = defineConfig({
|
||||
projectId: "vandxn",
|
||||
adminPassword: "admin",
|
||||
testUser: "frappe@example.com",
|
||||
defaultCommandTimeout: 20000,
|
||||
pageLoadTimeout: 15000,
|
||||
video: true,
|
||||
videoUploadOnPasses: false,
|
||||
retries: {
|
||||
runMode: 2,
|
||||
openMode: 0,
|
||||
},
|
||||
e2e: {
|
||||
baseUrl: "http://test_site_ui:8000",
|
||||
},
|
||||
});
|
||||
@@ -1,110 +0,0 @@
|
||||
describe("Course Creation", () => {
|
||||
it("creates a new course", () => {
|
||||
cy.login();
|
||||
cy.visit("/courses");
|
||||
// Create a course
|
||||
cy.get("a.btn").contains("Create a Course").click();
|
||||
cy.wait(1000);
|
||||
cy.url().should("include", "/courses/new-course/edit");
|
||||
cy.get("#title").type("Test Course");
|
||||
cy.get("#intro").type("Test Course Short Introduction");
|
||||
cy.get("#description").type("Test Course Description");
|
||||
cy.get("#video-link").type("-LPmw2Znl2c");
|
||||
cy.get("#tags-input").type("Test");
|
||||
cy.get("#published").check();
|
||||
cy.wait(1000);
|
||||
cy.button("Save").click();
|
||||
|
||||
// Add Chapter
|
||||
cy.wait(1000);
|
||||
cy.link("Course Outline").click();
|
||||
|
||||
cy.wait(1000);
|
||||
cy.get(".edit-header .btn-add-chapter").click();
|
||||
cy.get("#chapter-title").type("Test Chapter");
|
||||
cy.get("#chapter-description").type("Test Chapter Description");
|
||||
cy.button("Save").click();
|
||||
|
||||
// Add Lesson
|
||||
cy.wait(1000);
|
||||
cy.link("Add Lesson").click();
|
||||
cy.wait(1000);
|
||||
cy.get("#lesson-title").type("Test Lesson");
|
||||
|
||||
// Content
|
||||
cy.get(".ce-block").click().type("{enter}");
|
||||
cy.get(".ce-toolbar__plus").click();
|
||||
cy.get('[data-item-name="youtube"]').click();
|
||||
cy.get('input[data-fieldname="youtube"]').type("GoDtyItReto");
|
||||
cy.button("Insert").click();
|
||||
cy.wait(1000);
|
||||
|
||||
cy.get(".ce-block:last").click().type("{enter}");
|
||||
cy.get(".ce-block:last")
|
||||
.click()
|
||||
.type(
|
||||
"This is an extremely big paragraph that is meant to test the UI. This is a very long paragraph. It contains more than once sentence. Its meant to be this long as this is a UI test. Its unbearably long and I'm not sure why I'm typing this much. I'm just going to keep typing until I feel like its long enough. I think its long enough now. I'm going to stop typing now."
|
||||
);
|
||||
cy.button("Save").click();
|
||||
|
||||
// View Course
|
||||
cy.wait(1000);
|
||||
cy.visit("/courses");
|
||||
cy.get(".course-card-title:first").contains("Test Course");
|
||||
cy.get(".course-card:first").click();
|
||||
cy.url().should("include", "/courses/test-course");
|
||||
cy.get("#title").contains("Test Course");
|
||||
cy.get(".preview-video").should(
|
||||
"have.attr",
|
||||
"src",
|
||||
"https://www.youtube.com/embed/-LPmw2Znl2c"
|
||||
);
|
||||
cy.get("#intro").contains("Test Course Short Introduction");
|
||||
|
||||
// View Chapter
|
||||
cy.get(".chapter-title-main:first").contains("Test Chapter");
|
||||
cy.get(".chapter-description:first").contains(
|
||||
"Test Chapter Description"
|
||||
);
|
||||
cy.get(".lesson-info:first").contains("Test Lesson");
|
||||
cy.get(".lesson-info:first").click();
|
||||
|
||||
// View Lesson
|
||||
cy.wait(1000);
|
||||
cy.url().should("include", "learn/1.1");
|
||||
cy.get("#title").contains("Test Lesson");
|
||||
cy.get(".lesson-video iframe").should(
|
||||
"have.attr",
|
||||
"src",
|
||||
"https://www.youtube.com/embed/GoDtyItReto"
|
||||
);
|
||||
cy.get(".lesson-content-card").contains(
|
||||
"This is an extremely big paragraph that is meant to test the UI. This is a very long paragraph. It contains more than once sentence. Its meant to be this long as this is a UI test. Its unbearably long and I'm not sure why I'm typing this much. I'm just going to keep typing until I feel like its long enough. I think its long enough now. I'm going to stop typing now."
|
||||
);
|
||||
|
||||
// Add Discussion
|
||||
cy.get(".reply").click();
|
||||
cy.wait(500);
|
||||
cy.get(".topic-title").type("Question Title");
|
||||
cy.get(".comment-field").type(
|
||||
"Question Content. This is a very long question. It contains more than once sentence. Its meant to be this long as this is a UI test."
|
||||
);
|
||||
cy.get(".submit-discussion").click();
|
||||
|
||||
// View Discussion
|
||||
cy.wait(1000);
|
||||
cy.get(".discussion-topic-title:first").contains("Question Title");
|
||||
cy.get(".sidebar-parent:first").click();
|
||||
cy.get(".reply-text").contains(
|
||||
"Question Content. This is a very long question. It contains more than once sentence. Its meant to be this long as this is a UI test."
|
||||
);
|
||||
cy.get(".comment-field:visible").type(
|
||||
"This is a reply to the previous comment. Its not that long."
|
||||
);
|
||||
cy.get(".submit-discussion:visible").click();
|
||||
cy.wait(1000);
|
||||
cy.get(".reply-text:last p").contains(
|
||||
"This is a reply to the previous comment. Its not that long."
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add('login', (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||
|
||||
Cypress.Commands.add("login", (email, password) => {
|
||||
if (!email) {
|
||||
email = Cypress.config("testUser") || "Administrator";
|
||||
}
|
||||
if (!password) {
|
||||
password = Cypress.config("adminPassword");
|
||||
}
|
||||
cy.request({
|
||||
url: "/api/method/login",
|
||||
method: "POST",
|
||||
body: { usr: email, pwd: password },
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add("button", (text) => {
|
||||
return cy.get(`button:contains("${text}")`);
|
||||
});
|
||||
|
||||
Cypress.Commands.add("link", (text) => {
|
||||
return cy.get(`a:contains("${text}")`);
|
||||
});
|
||||
|
||||
Cypress.Commands.add("iconButton", (text) => {
|
||||
return cy.get(`button[aria-label="${text}"]`);
|
||||
});
|
||||
|
||||
Cypress.Commands.add("dialog", (selector) => {
|
||||
return cy.get(`[role=dialog] ${selector}`);
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
// ***********************************************************
|
||||
// This example support/e2e.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import "./commands";
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
@@ -609,13 +609,13 @@
|
||||
"in_list_view": 0,
|
||||
"in_preview": 0,
|
||||
"in_standard_filter": 0,
|
||||
"insert_after": "hide_private",
|
||||
"insert_after": "profession",
|
||||
"is_system_generated": 1,
|
||||
"is_virtual": 0,
|
||||
"label": "Education Details",
|
||||
"length": 0,
|
||||
"mandatory_depends_on": null,
|
||||
"modified": "2021-12-31 11:57:55.170620",
|
||||
"modified": "2021-12-31 11:57:55.170625",
|
||||
"module": null,
|
||||
"name": "User-education_details",
|
||||
"no_copy": 0,
|
||||
@@ -662,7 +662,7 @@
|
||||
"in_list_view": 0,
|
||||
"in_preview": 0,
|
||||
"in_standard_filter": 0,
|
||||
"insert_after": "bio",
|
||||
"insert_after": "hide_private",
|
||||
"is_system_generated": 1,
|
||||
"is_virtual": 0,
|
||||
"label": "Profile Complete",
|
||||
@@ -721,7 +721,7 @@
|
||||
"label": "Hide my Private Information from others",
|
||||
"length": 0,
|
||||
"mandatory_depends_on": null,
|
||||
"modified": "2021-12-31 11:57:47.942969",
|
||||
"modified": "2021-12-31 11:57:47.942968",
|
||||
"module": null,
|
||||
"name": "User-hide_my_private_information_from_others",
|
||||
"no_copy": 0,
|
||||
@@ -768,13 +768,13 @@
|
||||
"in_list_view": 0,
|
||||
"in_preview": 0,
|
||||
"in_standard_filter": 0,
|
||||
"insert_after": "user_category",
|
||||
"insert_after": "profile_complete",
|
||||
"is_system_generated": 1,
|
||||
"is_virtual": 0,
|
||||
"label": "Cover Image",
|
||||
"length": 0,
|
||||
"mandatory_depends_on": null,
|
||||
"modified": "2021-12-31 10:59:52.682115",
|
||||
"modified": "2021-12-31 10:59:52.682112",
|
||||
"module": null,
|
||||
"name": "User-cover_image",
|
||||
"no_copy": 0,
|
||||
@@ -821,13 +821,13 @@
|
||||
"in_list_view": 0,
|
||||
"in_preview": 0,
|
||||
"in_standard_filter": 0,
|
||||
"insert_after": "interest",
|
||||
"insert_after": "cover_image",
|
||||
"is_system_generated": 1,
|
||||
"is_virtual": 0,
|
||||
"label": "I am looking for a job",
|
||||
"length": 0,
|
||||
"mandatory_depends_on": null,
|
||||
"modified": "2021-12-31 12:56:32.110405",
|
||||
"modified": "2021-12-31 12:56:32.110403",
|
||||
"module": null,
|
||||
"name": "User-looking_for_job",
|
||||
"no_copy": 0,
|
||||
|
||||
12
lms/hooks.py
12
lms/hooks.py
@@ -60,7 +60,7 @@ web_include_js = ["website.bundle.js"]
|
||||
# before_install = "lms.install.before_install"
|
||||
after_install = "lms.install.after_install"
|
||||
after_sync = "lms.install.after_sync"
|
||||
before_uninstall = "lms.install.before_uninstall"
|
||||
after_uninstall = "lms.install.after_uninstall"
|
||||
|
||||
|
||||
setup_wizard_requires = "assets/lms/js/setup_wizard.js"
|
||||
@@ -138,18 +138,12 @@ fixtures = ["Custom Field", "Function", "Industry"]
|
||||
website_route_rules = [
|
||||
{"from_route": "/sketches/<sketch>", "to_route": "sketches/sketch"},
|
||||
{"from_route": "/courses/<course>", "to_route": "courses/course"},
|
||||
{"from_route": "/courses/<course>/edit", "to_route": "courses/create"},
|
||||
{"from_route": "/courses/<course>/outline", "to_route": "courses/outline"},
|
||||
{"from_route": "/courses/<course>/<certificate>", "to_route": "courses/certificate"},
|
||||
{"from_route": "/courses/<course>/learn", "to_route": "batch/learn"},
|
||||
{
|
||||
"from_route": "/courses/<course>/learn/<int:chapter>.<int:lesson>",
|
||||
"to_route": "batch/learn",
|
||||
},
|
||||
{
|
||||
"from_route": "/courses/<course>/learn/<int:chapter>.<int:lesson>/edit",
|
||||
"to_route": "batch/edit",
|
||||
},
|
||||
{"from_route": "/quizzes", "to_route": "batch/quiz_list"},
|
||||
{"from_route": "/quizzes/<quizname>", "to_route": "batch/quiz"},
|
||||
{"from_route": "/classes/<classname>", "to_route": "classes/class"},
|
||||
@@ -185,7 +179,6 @@ website_route_rules = [
|
||||
website_redirects = [
|
||||
{"source": "/update-profile", "target": "/edit-profile"},
|
||||
{"source": "/dashboard", "target": "/courses"},
|
||||
{"source": "/community", "target": "/people"},
|
||||
]
|
||||
|
||||
update_website_context = [
|
||||
@@ -235,7 +228,6 @@ jinja = {
|
||||
"lms.lms.utils.get_filtered_membership",
|
||||
"lms.lms.utils.show_start_learing_cta",
|
||||
"lms.lms.utils.can_create_courses",
|
||||
"lms.lms.utils.get_telemetry_boot_info",
|
||||
],
|
||||
"filters": [],
|
||||
}
|
||||
@@ -295,4 +287,6 @@ profile_url_prefix = "/users/"
|
||||
|
||||
signup_form_template = "lms.plugins.show_custom_signup"
|
||||
|
||||
on_login = "lms.overrides.user.on_login"
|
||||
|
||||
on_session_creation = "lms.overrides.user.on_session_creation"
|
||||
|
||||
@@ -44,9 +44,8 @@ def add_pages_to_nav():
|
||||
).save()
|
||||
|
||||
|
||||
def before_uninstall():
|
||||
def after_uninstall():
|
||||
delete_custom_fields()
|
||||
delete_lms_roles()
|
||||
|
||||
|
||||
def create_lms_roles():
|
||||
@@ -54,13 +53,6 @@ def create_lms_roles():
|
||||
create_moderator_role()
|
||||
|
||||
|
||||
def delete_lms_roles():
|
||||
roles = ["Course Creator", "Moderator"]
|
||||
for role in roles:
|
||||
if frappe.db.exists("Role", role):
|
||||
frappe.db.delete("Role", role)
|
||||
|
||||
|
||||
def set_default_home():
|
||||
frappe.db.set_value("Portal Settings", None, "default_portal_home", "/courses")
|
||||
|
||||
@@ -134,3 +126,4 @@ def delete_custom_fields():
|
||||
|
||||
for field in fields:
|
||||
frappe.db.delete("Custom Field", {"fieldname": field})
|
||||
frappe.db.commit()
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("Job Opportunity", {
|
||||
refresh: (frm) => {
|
||||
if (frm.doc.name)
|
||||
frm.add_web_link(`/jobs/${frm.doc.name}`, "See on Website");
|
||||
},
|
||||
// refresh: function(frm) {
|
||||
// }
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ def submit_solution(exercise, code):
|
||||
@exerecise: name of the exercise to submit
|
||||
@code: solution to the exercise
|
||||
"""
|
||||
ex = frappe.get_doc("LMS Exercise", exercise)
|
||||
ex = frappe.get_doc("Exercise", exercise)
|
||||
if not ex:
|
||||
return
|
||||
doc = ex.submit(code)
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils.telemetry import capture
|
||||
|
||||
|
||||
class CourseChapter(Document):
|
||||
def after_insert(self):
|
||||
capture("chapter_created", "lms")
|
||||
pass
|
||||
|
||||
@@ -7,7 +7,7 @@ frappe.ui.form.on("Course Lesson", {
|
||||
},
|
||||
setup_help(frm) {
|
||||
let quiz_link = `<a href="/app/lms-quiz"> ${__("Quiz List")} </a>`;
|
||||
let exercise_link = `<a href="/app/lms-exercise"> ${__(
|
||||
let exercise_link = `<a href="/app/exercise"> ${__(
|
||||
"Exercise List"
|
||||
)} </a>`;
|
||||
let file_link = `<a href="/app/file"> ${__("File DocType")} </a>`;
|
||||
|
||||
@@ -135,7 +135,7 @@
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-05-02 12:42:16.926753",
|
||||
"modified": "2022-12-28 16:01:42.191123",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "Course Lesson",
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils.telemetry import capture
|
||||
from lms.lms.utils import get_course_progress
|
||||
|
||||
from lms.lms.utils import get_course_progress, get_lesson_url
|
||||
|
||||
from ...md import find_macros
|
||||
|
||||
|
||||
@@ -23,11 +24,8 @@ class CourseLesson(Document):
|
||||
for section in dynamic_documents:
|
||||
self.update_lesson_name_in_document(section)
|
||||
|
||||
def after_insert(self):
|
||||
capture("lesson_created", "lms")
|
||||
|
||||
def update_lesson_name_in_document(self, section):
|
||||
doctype_map = {"Exercise": "LMS Exercise", "Quiz": "LMS Quiz"}
|
||||
doctype_map = {"Exercise": "Exercise", "Quiz": "LMS Quiz"}
|
||||
macros = find_macros(self.body)
|
||||
documents = [value for name, value in macros if name == section]
|
||||
index = 1
|
||||
@@ -55,7 +53,7 @@ class CourseLesson(Document):
|
||||
ex.course = None
|
||||
ex.index_ = 0
|
||||
ex.index_label = ""
|
||||
ex.save(ignore_permissions=True)
|
||||
ex.save()
|
||||
|
||||
def check_and_create_folder(self):
|
||||
args = {
|
||||
@@ -73,7 +71,7 @@ class CourseLesson(Document):
|
||||
|
||||
macros = find_macros(self.body)
|
||||
exercises = [value for name, value in macros if name == "Exercise"]
|
||||
return [frappe.get_doc("LMS Exercise", name) for name in exercises]
|
||||
return [frappe.get_doc("Exercise", name) for name in exercises]
|
||||
|
||||
def get_progress(self):
|
||||
return frappe.db.get_value(
|
||||
@@ -94,9 +92,14 @@ def save_progress(lesson, course, status):
|
||||
if not membership:
|
||||
return
|
||||
|
||||
filters = {"lesson": lesson, "owner": frappe.session.user, "course": course}
|
||||
if frappe.db.exists("LMS Course Progress", filters):
|
||||
doc = frappe.get_doc("LMS Course Progress", filters)
|
||||
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:
|
||||
@@ -105,7 +108,6 @@ def save_progress(lesson, course, status):
|
||||
"doctype": "LMS Course Progress",
|
||||
"lesson": lesson,
|
||||
"status": status,
|
||||
"member": frappe.session.user,
|
||||
}
|
||||
).save(ignore_permissions=True)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2021, FOSS United and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("LMS Exercise", {
|
||||
frappe.ui.form.on("Exercise", {
|
||||
// refresh: function(frm) {
|
||||
// }
|
||||
});
|
||||
@@ -99,7 +99,7 @@
|
||||
"modified": "2021-09-29 15:27:55.585874",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Exercise",
|
||||
"name": "Exercise",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@@ -120,4 +120,4 @@
|
||||
"sort_order": "ASC",
|
||||
"title_field": "title",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ from frappe.model.document import Document
|
||||
from lms.lms.utils import get_membership
|
||||
|
||||
|
||||
class LMSExercise(Document):
|
||||
class Exercise(Document):
|
||||
def get_user_submission(self):
|
||||
"""Returns the latest submission for this user."""
|
||||
user = frappe.session.user
|
||||
@@ -8,7 +8,7 @@ import frappe
|
||||
from lms.lms.doctype.lms_course.test_lms_course import new_course
|
||||
|
||||
|
||||
class TestLMSExercise(unittest.TestCase):
|
||||
class TestExercise(unittest.TestCase):
|
||||
def new_exercise(self):
|
||||
course = new_course("Test Course")
|
||||
member = frappe.get_doc(
|
||||
@@ -21,7 +21,7 @@ class TestLMSExercise(unittest.TestCase):
|
||||
member.insert()
|
||||
e = frappe.get_doc(
|
||||
{
|
||||
"doctype": "LMS Exercise",
|
||||
"doctype": "Exercise",
|
||||
"name": "test-problem",
|
||||
"course": course.name,
|
||||
"title": "Test Problem",
|
||||
@@ -51,4 +51,4 @@ class TestLMSExercise(unittest.TestCase):
|
||||
def tearDown(self):
|
||||
frappe.db.sql("delete from `tabLMS Batch Membership`")
|
||||
frappe.db.sql("delete from `tabExercise Submission`")
|
||||
frappe.db.sql("delete from `tabLMS Exercise`")
|
||||
frappe.db.sql("delete from `tabExercise`")
|
||||
@@ -30,7 +30,7 @@
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Exercise",
|
||||
"options": "LMS Exercise",
|
||||
"options": "Exercise",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
@@ -163,4 +163,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Exercise",
|
||||
"options": "LMS Exercise"
|
||||
"options": "Exercise"
|
||||
},
|
||||
{
|
||||
"fetch_from": "exercise.title",
|
||||
@@ -123,4 +123,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@
|
||||
"assignment",
|
||||
"lesson",
|
||||
"course",
|
||||
"evaluator",
|
||||
"status",
|
||||
"column_break_3",
|
||||
"member",
|
||||
@@ -57,11 +56,10 @@
|
||||
{
|
||||
"fetch_from": "lesson.course",
|
||||
"fieldname": "course",
|
||||
"fieldtype": "Link",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Course",
|
||||
"options": "LMS Course",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@@ -75,20 +73,12 @@
|
||||
"fieldname": "comments",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Comments"
|
||||
},
|
||||
{
|
||||
"fetch_from": "course.evaluator",
|
||||
"fieldname": "evaluator",
|
||||
"fieldtype": "Link",
|
||||
"label": "Evaluator",
|
||||
"options": "User",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"make_attachments_public": 1,
|
||||
"modified": "2023-03-27 13:24:18.696868",
|
||||
"modified": "2022-11-16 12:11:59.472025",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "Lesson Assignment",
|
||||
|
||||
@@ -58,4 +58,4 @@ def grade_assignment(name, result, comments):
|
||||
doc = frappe.get_doc("Lesson Assignment", name)
|
||||
doc.status = result
|
||||
doc.comments = comments
|
||||
doc.save(ignore_permissions=True)
|
||||
doc.save()
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-04-14 12:33:37.839625",
|
||||
"modified": "2022-04-06 11:49:36.077370",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Certificate",
|
||||
|
||||
@@ -5,14 +5,13 @@ import frappe
|
||||
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 validate(self):
|
||||
self.validate_duplicate_certificate()
|
||||
|
||||
def validate_duplicate_certificate(self):
|
||||
def before_insert(self):
|
||||
certificates = frappe.get_all(
|
||||
"LMS Certificate", {"member": self.member, "course": self.course}
|
||||
)
|
||||
@@ -23,18 +22,6 @@ class LMSCertificate(Document):
|
||||
_("{0} is already certified for the course {1}").format(full_name, course_name)
|
||||
)
|
||||
|
||||
def after_insert(self):
|
||||
share = frappe.get_doc(
|
||||
{
|
||||
"doctype": "DocShare",
|
||||
"read": 1,
|
||||
"share_doctype": "LMS Certificate",
|
||||
"share_name": self.name,
|
||||
"user": self.member,
|
||||
}
|
||||
)
|
||||
share.save(ignore_permissions=True)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_certificate(course):
|
||||
@@ -60,3 +47,10 @@ def create_certificate(course):
|
||||
)
|
||||
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"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
frappe.ui.form.on("LMS Certificate Evaluation", {
|
||||
refresh: function (frm) {
|
||||
if (!frm.is_new() && frm.doc.status == "Pass") {
|
||||
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",
|
||||
|
||||
@@ -9,9 +9,8 @@
|
||||
"member",
|
||||
"member_name",
|
||||
"column_break_5",
|
||||
"status",
|
||||
"course",
|
||||
"class",
|
||||
"status",
|
||||
"section_break_6",
|
||||
"date",
|
||||
"start_time",
|
||||
@@ -94,17 +93,11 @@
|
||||
{
|
||||
"fieldname": "column_break_10",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "class",
|
||||
"fieldtype": "Link",
|
||||
"label": "Class",
|
||||
"options": "LMS Class"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-02-22 16:00:34.361934",
|
||||
"modified": "2022-11-23 11:49:01.400292",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Certificate Evaluation",
|
||||
|
||||
@@ -3,17 +3,12 @@
|
||||
|
||||
frappe.ui.form.on("LMS Certificate Request", {
|
||||
refresh: function (frm) {
|
||||
if (!frm.is_new()) {
|
||||
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.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,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
onload: function (frm) {
|
||||
|
||||
@@ -7,14 +7,12 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"course",
|
||||
"evaluator",
|
||||
"column_break_4",
|
||||
"member",
|
||||
"member_name",
|
||||
"section_break_lifi",
|
||||
"evaluator",
|
||||
"column_break_4",
|
||||
"date",
|
||||
"day",
|
||||
"column_break_ddyh",
|
||||
"start_time",
|
||||
"end_time"
|
||||
],
|
||||
@@ -31,7 +29,7 @@
|
||||
{
|
||||
"fieldname": "member",
|
||||
"fieldtype": "Link",
|
||||
"in_standard_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Member",
|
||||
"options": "User",
|
||||
"reqd": 1
|
||||
@@ -41,7 +39,7 @@
|
||||
"fieldname": "evaluator",
|
||||
"fieldtype": "Link",
|
||||
"label": "Evaluator",
|
||||
"options": "User",
|
||||
"options": "Course Evaluator",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@@ -68,6 +66,7 @@
|
||||
{
|
||||
"fieldname": "end_time",
|
||||
"fieldtype": "Time",
|
||||
"in_list_view": 1,
|
||||
"label": "End Time",
|
||||
"reqd": 1
|
||||
},
|
||||
@@ -81,19 +80,11 @@
|
||||
"fieldtype": "Data",
|
||||
"label": "Member Name",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_lifi",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_ddyh",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-02-28 19:53:17.534351",
|
||||
"modified": "2022-04-06 11:33:33.711545",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Certificate Request",
|
||||
@@ -108,7 +99,6 @@
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"select": 1,
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
|
||||
@@ -12,10 +12,6 @@ class LMSCertificateRequest(Document):
|
||||
def validate(self):
|
||||
self.validate_if_existing_requests()
|
||||
|
||||
def after_insert(self):
|
||||
if frappe.db.get_single_value("LMS Settings", "send_calendar_invite_for_evaluations"):
|
||||
self.create_event()
|
||||
|
||||
def validate_if_existing_requests(self):
|
||||
existing_requests = frappe.get_all(
|
||||
"LMS Certificate Request",
|
||||
@@ -34,48 +30,6 @@ class LMSCertificateRequest(Document):
|
||||
)
|
||||
)
|
||||
|
||||
def create_event(self):
|
||||
calendar = frappe.db.get_value(
|
||||
"Google Calendar", {"user": self.evaluator, "enable": 1}, "name"
|
||||
)
|
||||
|
||||
if calendar:
|
||||
event = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Event",
|
||||
"subject": f"Evaluation of {self.member_name}",
|
||||
"starts_on": f"{self.date} {self.start_time}",
|
||||
"ends_on": f"{self.date} {self.end_time}",
|
||||
}
|
||||
)
|
||||
event.save()
|
||||
|
||||
participants = [self.member, self.evaluator]
|
||||
for participant in participants:
|
||||
contact_name = frappe.db.get_value("Contact", {"email_id": participant}, "name")
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Event Participants",
|
||||
"reference_doctype": "Contact",
|
||||
"reference_docname": contact_name,
|
||||
"email": participant,
|
||||
"parent": event.name,
|
||||
"parenttype": "Event",
|
||||
"parentfield": "event_participants",
|
||||
}
|
||||
).save()
|
||||
|
||||
event.reload()
|
||||
event.update(
|
||||
{
|
||||
"sync_with_google_calendar": 1,
|
||||
"add_video_conferencing": 1,
|
||||
"google_calendar": calendar,
|
||||
}
|
||||
)
|
||||
|
||||
event.save()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_certificate_request(course, date, day, start_time, end_time):
|
||||
|
||||
@@ -2,13 +2,6 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("LMS Class", {
|
||||
onload: function (frm) {
|
||||
frm.set_query("student", "students", function (doc) {
|
||||
return {
|
||||
filters: {
|
||||
ignore_user_type: 1,
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
// refresh: function(frm) {
|
||||
// }
|
||||
});
|
||||
|
||||
@@ -11,13 +11,9 @@
|
||||
"title",
|
||||
"start_date",
|
||||
"end_date",
|
||||
"paid_class",
|
||||
"column_break_4",
|
||||
"seat_count",
|
||||
"start_time",
|
||||
"end_time",
|
||||
"section_break_6",
|
||||
"description",
|
||||
"section_break_6",
|
||||
"students",
|
||||
"courses",
|
||||
"custom_component"
|
||||
@@ -33,7 +29,6 @@
|
||||
{
|
||||
"fieldname": "end_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "End Date",
|
||||
"reqd": 1
|
||||
},
|
||||
@@ -65,7 +60,6 @@
|
||||
{
|
||||
"fieldname": "start_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Start Date",
|
||||
"reqd": 1
|
||||
},
|
||||
@@ -75,32 +69,11 @@
|
||||
"fieldtype": "Code",
|
||||
"label": "Custom Component",
|
||||
"options": "HTML"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "paid_class",
|
||||
"fieldtype": "Check",
|
||||
"label": "Paid Class"
|
||||
},
|
||||
{
|
||||
"fieldname": "seat_count",
|
||||
"fieldtype": "Int",
|
||||
"label": "Seat Count"
|
||||
},
|
||||
{
|
||||
"fieldname": "start_time",
|
||||
"fieldtype": "Time",
|
||||
"label": "Start Time"
|
||||
},
|
||||
{
|
||||
"fieldname": "end_time",
|
||||
"fieldtype": "Time",
|
||||
"label": "End Time"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-05-03 23:07:06.725720",
|
||||
"modified": "2022-11-25 10:37:24.250557",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Class",
|
||||
@@ -134,6 +107,5 @@
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "title"
|
||||
"states": []
|
||||
}
|
||||
@@ -4,43 +4,24 @@
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe import _
|
||||
from frappe.utils import cint, format_date, format_datetime
|
||||
import requests
|
||||
import base64
|
||||
import json
|
||||
from frappe.utils import cint
|
||||
|
||||
|
||||
class LMSClass(Document):
|
||||
def validate(self):
|
||||
if self.seat_count:
|
||||
self.validate_seats_left()
|
||||
self.validate_duplicate_students()
|
||||
self.validate_membership()
|
||||
validate_membership(self)
|
||||
|
||||
def validate_duplicate_students(self):
|
||||
students = [row.student for row in self.students]
|
||||
duplicates = {student for student in students if students.count(student) > 1}
|
||||
if len(duplicates):
|
||||
frappe.throw(
|
||||
_("Student {0} has already been added to this class.").format(
|
||||
frappe.bold(next(iter(duplicates)))
|
||||
)
|
||||
)
|
||||
|
||||
def validate_membership(self):
|
||||
for course in self.courses:
|
||||
for student in self.students:
|
||||
filters = {
|
||||
"doctype": "LMS Batch Membership",
|
||||
"member": student.student,
|
||||
"course": course.course,
|
||||
}
|
||||
if not frappe.db.exists(filters):
|
||||
frappe.get_doc(filters).save()
|
||||
|
||||
def validate_seats_left(self):
|
||||
if cint(self.seat_count) < len(self.students):
|
||||
frappe.throw(_("There are no seats available in this class."))
|
||||
def validate_membership(self):
|
||||
for course in self.courses:
|
||||
for student in self.students:
|
||||
filters = {
|
||||
"doctype": "LMS Batch Membership",
|
||||
"member": student.student,
|
||||
"course": course.course,
|
||||
}
|
||||
if not frappe.db.exists(filters):
|
||||
frappe.get_doc(filters).save()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@@ -48,17 +29,6 @@ def add_student(email, class_name):
|
||||
if not frappe.db.exists("User", email):
|
||||
frappe.throw(_("There is no such user. Please create a user with this Email ID."))
|
||||
|
||||
filters = {
|
||||
"student": email,
|
||||
"parent": class_name,
|
||||
"parenttype": "LMS Class",
|
||||
"parentfield": "students",
|
||||
}
|
||||
if frappe.db.exists("Class Student", filters):
|
||||
frappe.throw(
|
||||
_("Student {0} has already been added to this class.").format(frappe.bold(email))
|
||||
)
|
||||
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Class Student",
|
||||
@@ -75,108 +45,22 @@ def add_student(email, class_name):
|
||||
@frappe.whitelist()
|
||||
def remove_student(student, class_name):
|
||||
frappe.db.delete("Class Student", {"student": student, "parent": class_name})
|
||||
return True
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def remove_course(course, parent):
|
||||
frappe.db.delete("Class Course", {"course": course, "parent": parent})
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_live_class(
|
||||
class_name, title, duration, date, time, timezone, auto_recording, description=None
|
||||
):
|
||||
date = format_date(date, "yyyy-mm-dd", True)
|
||||
|
||||
payload = {
|
||||
"topic": title,
|
||||
"start_time": format_datetime(f"{date} {time}", "yyyy-MM-ddTHH:mm:ssZ"),
|
||||
"duration": duration,
|
||||
"agenda": description,
|
||||
"private_meeting": True,
|
||||
"auto_recording": "none"
|
||||
if auto_recording == "No Recording"
|
||||
else auto_recording.lower(),
|
||||
"timezone": timezone,
|
||||
}
|
||||
headers = {
|
||||
"Authorization": "Bearer " + authenticate(),
|
||||
"content-type": "application/json",
|
||||
}
|
||||
response = requests.post(
|
||||
"https://api.zoom.us/v2/users/me/meetings", headers=headers, data=json.dumps(payload)
|
||||
)
|
||||
|
||||
if response.status_code == 201:
|
||||
data = json.loads(response.text)
|
||||
payload.update(
|
||||
def update_course(class_name, course, value):
|
||||
if cint(value):
|
||||
doc = frappe.get_doc(
|
||||
{
|
||||
"doctype": "LMS Live Class",
|
||||
"start_url": data.get("start_url"),
|
||||
"join_url": data.get("join_url"),
|
||||
"title": title,
|
||||
"host": frappe.session.user,
|
||||
"date": date,
|
||||
"time": time,
|
||||
"class_name": class_name,
|
||||
"password": data.get("password"),
|
||||
"description": description,
|
||||
"auto_recording": auto_recording,
|
||||
"doctype": "Class Course",
|
||||
"parent": class_name,
|
||||
"course": course,
|
||||
"parenttype": "LMS Class",
|
||||
"parentfield": "courses",
|
||||
}
|
||||
)
|
||||
class_details = frappe.get_doc(payload)
|
||||
class_details.save()
|
||||
return class_details
|
||||
|
||||
|
||||
def authenticate():
|
||||
zoom = frappe.get_single("Zoom Settings")
|
||||
if not zoom.enable:
|
||||
frappe.throw(_("Please enable Zoom Settings to use this feature."))
|
||||
|
||||
authenticate_url = f"https://zoom.us/oauth/token?grant_type=account_credentials&account_id={zoom.account_id}"
|
||||
|
||||
headers = {
|
||||
"Authorization": "Basic "
|
||||
+ base64.b64encode(
|
||||
bytes(
|
||||
zoom.client_id
|
||||
+ ":"
|
||||
+ zoom.get_password(fieldname="client_secret", raise_exception=False),
|
||||
encoding="utf8",
|
||||
)
|
||||
).decode()
|
||||
}
|
||||
response = requests.request("POST", authenticate_url, headers=headers)
|
||||
return response.json()["access_token"]
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_class(
|
||||
title,
|
||||
start_date,
|
||||
end_date,
|
||||
description=None,
|
||||
seat_count=0,
|
||||
start_time=None,
|
||||
end_time=None,
|
||||
name=None,
|
||||
):
|
||||
if name:
|
||||
class_details = frappe.get_doc("LMS Class", name)
|
||||
doc.save()
|
||||
else:
|
||||
class_details = frappe.get_doc({"doctype": "LMS Class"})
|
||||
|
||||
class_details.update(
|
||||
{
|
||||
"title": title,
|
||||
"start_date": start_date,
|
||||
"end_date": end_date,
|
||||
"description": description,
|
||||
"seat_count": seat_count,
|
||||
"start_time": start_time,
|
||||
"end_time": end_time,
|
||||
}
|
||||
)
|
||||
class_details.save()
|
||||
return class_details
|
||||
frappe.db.delete("Class Course", {"parent": class_name, "course": course})
|
||||
return True
|
||||
|
||||
@@ -27,7 +27,4 @@ frappe.ui.form.on("LMS Course", {
|
||||
};
|
||||
});
|
||||
},
|
||||
refresh: (frm) => {
|
||||
frm.add_web_link(`/courses/${frm.doc.name}`, "See on Website");
|
||||
},
|
||||
});
|
||||
|
||||
@@ -53,11 +53,12 @@
|
||||
"in_list_view": 1,
|
||||
"label": "Title",
|
||||
"reqd": 1,
|
||||
"unique": 1,
|
||||
"width": "200"
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Text Editor",
|
||||
"fieldtype": "Markdown Editor",
|
||||
"label": "Description",
|
||||
"reqd": 1
|
||||
},
|
||||
@@ -260,7 +261,7 @@
|
||||
}
|
||||
],
|
||||
"make_attachments_public": 1,
|
||||
"modified": "2023-05-11 17:08:19.763405",
|
||||
"modified": "2022-09-14 13:26:53.153822",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Course",
|
||||
|
||||
@@ -2,19 +2,19 @@
|
||||
# For license information, please see license.txt
|
||||
|
||||
import json
|
||||
import random
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cint
|
||||
from frappe.utils.telemetry import capture
|
||||
|
||||
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_video_link()
|
||||
self.validate_status()
|
||||
self.image = validate_image(self.image)
|
||||
|
||||
@@ -30,10 +30,6 @@ class LMSCourse(Document):
|
||||
}
|
||||
).save(ignore_permissions=True)
|
||||
|
||||
def validate_video_link(self):
|
||||
if self.video_link and "/" in self.video_link:
|
||||
self.video_link = self.video_link.split("/")[-1]
|
||||
|
||||
def validate_status(self):
|
||||
if self.published:
|
||||
self.status = "Approved"
|
||||
@@ -42,9 +38,6 @@ class LMSCourse(Document):
|
||||
if not self.upcoming and self.has_value_changed("upcoming"):
|
||||
self.send_email_to_interested_users()
|
||||
|
||||
def after_insert(self):
|
||||
capture("course_created", "lms")
|
||||
|
||||
def send_email_to_interested_users(self):
|
||||
interested_users = frappe.get_all(
|
||||
"LMS Course Interest", {"course": self.name}, ["name", "user"]
|
||||
@@ -74,10 +67,7 @@ class LMSCourse(Document):
|
||||
|
||||
def autoname(self):
|
||||
if not self.name:
|
||||
title = self.title
|
||||
if self.title == "New Course":
|
||||
title = self.title + str(random.randint(0, 99))
|
||||
self.name = generate_slug(title, "LMS Course")
|
||||
self.name = generate_slug(self.title, "LMS Course")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Course#{self.name}>"
|
||||
@@ -311,43 +301,3 @@ def save_lesson(
|
||||
lesson_reference.save(ignore_permissions=True)
|
||||
|
||||
return doc.name
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def reorder_lesson(old_chapter, old_lesson_array, new_chapter, new_lesson_array):
|
||||
if old_chapter == new_chapter:
|
||||
sort_lessons(new_chapter, new_lesson_array)
|
||||
else:
|
||||
sort_lessons(old_chapter, old_lesson_array)
|
||||
sort_lessons(new_chapter, new_lesson_array)
|
||||
|
||||
|
||||
def sort_lessons(chapter, lesson_array):
|
||||
lesson_array = json.loads(lesson_array)
|
||||
for les in lesson_array:
|
||||
ref = frappe.get_all("Lesson Reference", {"lesson": les}, ["name", "idx"])
|
||||
if ref:
|
||||
frappe.db.set_value(
|
||||
"Lesson Reference",
|
||||
ref[0].name,
|
||||
{
|
||||
"parent": chapter,
|
||||
"idx": lesson_array.index(les) + 1,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def reorder_chapter(chapter_array):
|
||||
chapter_array = json.loads(chapter_array)
|
||||
|
||||
for chap in chapter_array:
|
||||
ref = frappe.get_all("Chapter Reference", {"chapter": chap}, ["name", "idx"])
|
||||
if ref:
|
||||
frappe.db.set_value(
|
||||
"Chapter Reference",
|
||||
ref[0].name,
|
||||
{
|
||||
"idx": chapter_array.index(chap) + 1,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -35,7 +35,7 @@ class TestLMSCourse(unittest.TestCase):
|
||||
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("LMS Exercise", {"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"})
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
// Copyright (c) 2023, Frappe and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("LMS Live Class", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@@ -1,164 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2023-03-02 10:59:01.741349",
|
||||
"default_view": "List",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title",
|
||||
"host",
|
||||
"class_name",
|
||||
"password",
|
||||
"auto_recording",
|
||||
"column_break_astv",
|
||||
"description",
|
||||
"section_break_glxh",
|
||||
"date",
|
||||
"timezone",
|
||||
"column_break_spvt",
|
||||
"time",
|
||||
"duration",
|
||||
"section_break_yrpq",
|
||||
"start_url",
|
||||
"column_break_yokr",
|
||||
"join_url"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Title",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Text",
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"fieldname": "date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "duration",
|
||||
"fieldtype": "Int",
|
||||
"label": "Duration",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "timezone",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Timezone",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "host",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Host",
|
||||
"options": "User",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_astv",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_glxh",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Date and Time"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_spvt",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_yrpq",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "start_url",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Start URL",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_yokr",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "join_url",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Join URL",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "password",
|
||||
"fieldtype": "Password",
|
||||
"label": "Password"
|
||||
},
|
||||
{
|
||||
"fieldname": "time",
|
||||
"fieldtype": "Time",
|
||||
"label": "Time",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "class_name",
|
||||
"fieldtype": "Link",
|
||||
"label": "Class",
|
||||
"options": "LMS Class"
|
||||
},
|
||||
{
|
||||
"default": "No Recording",
|
||||
"fieldname": "auto_recording",
|
||||
"fieldtype": "Select",
|
||||
"label": "Auto Recording",
|
||||
"options": "No Recording\nLocal\nCloud"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-03-14 18:44:48.813102",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Live Class",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Moderator",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
# Copyright (c) 2023, Frappe and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from datetime import timedelta
|
||||
from frappe.utils import cint, get_datetime
|
||||
|
||||
|
||||
class LMSLiveClass(Document):
|
||||
def after_insert(self):
|
||||
calendar = frappe.db.get_value(
|
||||
"Google Calendar", {"user": frappe.session.user, "enable": 1}, "name"
|
||||
)
|
||||
|
||||
if calendar:
|
||||
event = self.create_event()
|
||||
self.add_event_participants(event, calendar)
|
||||
|
||||
def create_event(self):
|
||||
start = f"{self.date} {self.time}"
|
||||
|
||||
event = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Event",
|
||||
"subject": f"Live Class on {self.title}",
|
||||
"starts_on": start,
|
||||
"ends_on": get_datetime(start) + timedelta(minutes=cint(self.duration)),
|
||||
}
|
||||
)
|
||||
event.save()
|
||||
|
||||
return event
|
||||
|
||||
def add_event_participants(self, event, calendar):
|
||||
participants = frappe.get_all(
|
||||
"Class Student", {"parent": self.class_name}, pluck="student"
|
||||
)
|
||||
|
||||
participants.append(frappe.session.user)
|
||||
for participant in participants:
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Event Participants",
|
||||
"reference_doctype": "User",
|
||||
"reference_docname": participant,
|
||||
"email": participant,
|
||||
"parent": event.name,
|
||||
"parenttype": "Event",
|
||||
"parentfield": "event_participants",
|
||||
}
|
||||
).save()
|
||||
|
||||
event.reload()
|
||||
event.update(
|
||||
{
|
||||
"sync_with_google_calendar": 1,
|
||||
"google_calendar": calendar,
|
||||
"description": f"A Live Class has been scheduled on {frappe.utils.format_date(self.date, 'medium')} at { frappe.utils.format_time(self.time, 'hh:mm a')}. Click on this link to join. {self.join_url}. {self.description}",
|
||||
}
|
||||
)
|
||||
|
||||
event.save()
|
||||
@@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2023, Frappe and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
|
||||
class TestLMSLiveClass(FrappeTestCase):
|
||||
pass
|
||||
@@ -21,39 +21,17 @@ class LMSQuiz(Document):
|
||||
|
||||
def validate_correct_answers(self):
|
||||
for question in self.questions:
|
||||
if question.type == "Choices":
|
||||
self.validate_correct_options(question)
|
||||
else:
|
||||
self.validate_possible_answer(question)
|
||||
correct_options = self.get_correct_options(question)
|
||||
|
||||
def validate_correct_options(self, question):
|
||||
correct_options = self.get_correct_options(question)
|
||||
if len(correct_options) > 1:
|
||||
question.multiple = 1
|
||||
|
||||
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)
|
||||
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_possible_answer(self, question):
|
||||
possible_answers_fields = [
|
||||
"possibility_1",
|
||||
"possibility_2",
|
||||
"possibility_3",
|
||||
"possibility_4",
|
||||
]
|
||||
possible_answers = list(filter(lambda x: question.get(x), possible_answers_fields))
|
||||
|
||||
if not len(possible_answers):
|
||||
frappe.throw(
|
||||
_("Add at least one possible answer for this question: {0}").format(
|
||||
frappe.bold(question.question)
|
||||
)
|
||||
)
|
||||
|
||||
def get_correct_options(self, question):
|
||||
correct_option_fields = [
|
||||
@@ -148,54 +126,17 @@ def save_quiz(quiz_title, questions, quiz):
|
||||
}
|
||||
)
|
||||
|
||||
question_doc.update(row)
|
||||
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)],
|
||||
}
|
||||
)
|
||||
|
||||
question_doc.save(ignore_permissions=True)
|
||||
|
||||
return doc.name
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def check_answer(question, type, answer):
|
||||
if type == "Choices":
|
||||
return check_choice_answers(question, answer)
|
||||
else:
|
||||
return check_input_answers(question, answer)
|
||||
|
||||
|
||||
def check_choice_answers(question, answer):
|
||||
fields = []
|
||||
for num in range(1, 5):
|
||||
fields.append(f"option_{cstr(num)}")
|
||||
fields.append(f"is_correct_{cstr(num)}")
|
||||
|
||||
question_details = frappe.db.get_value(
|
||||
"LMS Quiz Question", question, fields, as_dict=1
|
||||
)
|
||||
|
||||
for num in range(1, 5):
|
||||
if question_details[f"option_{num}"] == answer:
|
||||
return question_details[f"is_correct_{num}"]
|
||||
return 0
|
||||
|
||||
|
||||
def check_input_answers(question, answer):
|
||||
fields = []
|
||||
for num in range(1, 5):
|
||||
fields.append(f"possibility_{cstr(num)}")
|
||||
|
||||
question_details = frappe.db.get_value(
|
||||
"LMS Quiz Question", question, fields, as_dict=1
|
||||
)
|
||||
for num in range(1, 5):
|
||||
current_possibility = question_details[f"possibility_{num}"]
|
||||
if current_possibility and current_possibility.lower() == answer.lower():
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_user_quizzes():
|
||||
return frappe.get_all(
|
||||
"LMS Quiz", filters={"owner": frappe.session.user}, fields=["name", "title"]
|
||||
)
|
||||
|
||||
@@ -19,8 +19,7 @@ class TestLMSQuiz(unittest.TestCase):
|
||||
quiz.append(
|
||||
"questions",
|
||||
{
|
||||
"question": "Question Multiple",
|
||||
"type": "Choices",
|
||||
"question": "Question multiple",
|
||||
"option_1": "Option 1",
|
||||
"is_correct_1": 1,
|
||||
"option_2": "Option 2",
|
||||
@@ -36,24 +35,12 @@ class TestLMSQuiz(unittest.TestCase):
|
||||
"questions",
|
||||
{
|
||||
"question": "Question no correct option",
|
||||
"type": "Choices",
|
||||
"option_1": "Option 1",
|
||||
"option_2": "Option 2",
|
||||
},
|
||||
)
|
||||
self.assertRaises(frappe.ValidationError, quiz.save)
|
||||
|
||||
def test_with_no_possible_answers(self):
|
||||
quiz = frappe.get_doc("LMS Quiz", "test-quiz")
|
||||
quiz.append(
|
||||
"questions",
|
||||
{
|
||||
"question": "Question Possible Answers",
|
||||
"type": "User Input",
|
||||
},
|
||||
)
|
||||
self.assertRaises(frappe.ValidationError, quiz.save)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls) -> None:
|
||||
frappe.db.delete("LMS Quiz", "test-quiz")
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"question",
|
||||
"type",
|
||||
"options_section",
|
||||
"option_1",
|
||||
"is_correct_1",
|
||||
@@ -27,13 +26,6 @@
|
||||
"is_correct_4",
|
||||
"column_break_20",
|
||||
"explanation_4",
|
||||
"section_break_mnhr",
|
||||
"possibility_1",
|
||||
"possibility_3",
|
||||
"column_break_vnaj",
|
||||
"possibility_2",
|
||||
"possibility_4",
|
||||
"section_break_c1lf",
|
||||
"multiple"
|
||||
],
|
||||
"fields": [
|
||||
@@ -48,13 +40,13 @@
|
||||
"fieldname": "option_1",
|
||||
"fieldtype": "Data",
|
||||
"label": "Option 1",
|
||||
"mandatory_depends_on": "eval: doc.type == 'Choices'"
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "option_2",
|
||||
"fieldtype": "Data",
|
||||
"label": "Option 2",
|
||||
"mandatory_depends_on": "eval: doc.type == 'Choices'"
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "option_3",
|
||||
@@ -103,22 +95,18 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.type == 'Choices'",
|
||||
"fieldname": "options_section",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.type == 'Choices'",
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.type == 'Choices'",
|
||||
"fieldname": "section_break_5",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.type == 'Choices'",
|
||||
"fieldname": "section_break_11",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
@@ -161,52 +149,12 @@
|
||||
{
|
||||
"fieldname": "column_break_20",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Type",
|
||||
"options": "Choices\nUser Input"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.type == 'User Input'",
|
||||
"fieldname": "section_break_mnhr",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "possibility_1",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Possible Answer 1",
|
||||
"mandatory_depends_on": "eval: doc.type == 'User Input'"
|
||||
},
|
||||
{
|
||||
"fieldname": "possibility_2",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Possible Answer 2"
|
||||
},
|
||||
{
|
||||
"fieldname": "possibility_3",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Possible Answer 3"
|
||||
},
|
||||
{
|
||||
"fieldname": "possibility_4",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Possible Answer 4"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_c1lf",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_vnaj",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-03-17 18:22:20.324536",
|
||||
"modified": "2021-07-19 19:35:28.446236",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Quiz Question",
|
||||
@@ -214,6 +162,5 @@
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -11,7 +11,7 @@ class LMSSection(Document):
|
||||
|
||||
def get_exercise(self):
|
||||
if self.type == "exercise":
|
||||
return frappe.get_doc("LMS Exercise", self.id)
|
||||
return frappe.get_doc("Exercise", self.id)
|
||||
|
||||
def get_quiz(self):
|
||||
if self.type == "quiz":
|
||||
|
||||
@@ -5,18 +5,13 @@
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"default_home",
|
||||
"send_calendar_invite_for_evaluations",
|
||||
"allow_student_progress",
|
||||
"column_break_zdel",
|
||||
"is_onboarding_complete",
|
||||
"force_profile_completion",
|
||||
"section_break_szgq",
|
||||
"search_placeholder",
|
||||
"portal_course_creation",
|
||||
"is_onboarding_complete",
|
||||
"column_break_2",
|
||||
"custom_certificate_template",
|
||||
"livecode_url",
|
||||
"signup_settings_tab",
|
||||
"force_profile_completion",
|
||||
"signup_settings_section",
|
||||
"terms_of_use",
|
||||
"terms_page",
|
||||
@@ -27,7 +22,6 @@
|
||||
"column_break_12",
|
||||
"cookie_policy",
|
||||
"cookie_policy_page",
|
||||
"mentor_request_tab",
|
||||
"mentor_request_section",
|
||||
"mentor_request_creation",
|
||||
"mentor_request_status_update"
|
||||
@@ -87,7 +81,8 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "signup_settings_section",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Signup Settings"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@@ -138,54 +133,24 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Ask User Category during Signup"
|
||||
},
|
||||
{
|
||||
"fieldname": "custom_certificate_template",
|
||||
"fieldtype": "Link",
|
||||
"label": "Custom Certificate Template",
|
||||
"options": "Web Template"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_onboarding_complete",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Onboarding Complete",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "default_home",
|
||||
"fieldtype": "Check",
|
||||
"label": "Make LMS the default home"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_zdel",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "send_calendar_invite_for_evaluations",
|
||||
"fieldtype": "Check",
|
||||
"label": "Send calendar invite for evaluations"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_szgq",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "signup_settings_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Signup Settings"
|
||||
},
|
||||
{
|
||||
"fieldname": "mentor_request_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Mentor Request"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_student_progress",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow students to see each others progress in class"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-04-17 12:54:44.706101",
|
||||
"modified": "2022-12-20 11:44:06.317159",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Settings",
|
||||
|
||||
@@ -4,38 +4,7 @@
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import get_url_to_list
|
||||
|
||||
|
||||
class LMSSettings(Document):
|
||||
def validate(self):
|
||||
self.validate_google_settings()
|
||||
|
||||
def validate_google_settings(self):
|
||||
if self.send_calendar_invite_for_evaluations:
|
||||
google_settings = frappe.get_single("Google Settings")
|
||||
|
||||
if not google_settings.enable:
|
||||
frappe.throw(
|
||||
_("Enable Google API in Google Settings to send calendar invites for evaluations.")
|
||||
)
|
||||
|
||||
if not google_settings.client_id or not google_settings.client_secret:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Enter Client Id and Client Secret in Google Settings to send calendar invites for evaluations."
|
||||
)
|
||||
)
|
||||
|
||||
calendars = frappe.db.count("Google Calendar")
|
||||
if not calendars:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Please add <a href='{0}'>{1}</a> for <a href='{2}'>{3}</a> to send calendar invites for evaluations."
|
||||
).format(
|
||||
get_url_to_list("Google Calendar"),
|
||||
frappe.bold("Google Calendar"),
|
||||
get_url_to_list("Course Evaluator"),
|
||||
frappe.bold("Course Evaluator"),
|
||||
)
|
||||
)
|
||||
pass
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2023, Frappe and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
|
||||
class TestZoomSettings(FrappeTestCase):
|
||||
pass
|
||||
@@ -1,8 +0,0 @@
|
||||
// Copyright (c) 2023, Frappe and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Zoom Settings", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@@ -1,71 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2023-02-27 14:30:28.696814",
|
||||
"default_view": "List",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"enable",
|
||||
"sb_00",
|
||||
"account_id",
|
||||
"client_id",
|
||||
"client_secret"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "enable",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable"
|
||||
},
|
||||
{
|
||||
"depends_on": "enable",
|
||||
"fieldname": "sb_00",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "OAuth Client ID"
|
||||
},
|
||||
{
|
||||
"description": "The Client ID obtained from the Google Cloud Console under <a href=\"https://console.cloud.google.com/apis/credentials\">\n\"APIs & Services\" > \"Credentials\"\n</a>",
|
||||
"fieldname": "client_id",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Client ID",
|
||||
"mandatory_depends_on": "google_drive_picker_enabled"
|
||||
},
|
||||
{
|
||||
"fieldname": "client_secret",
|
||||
"fieldtype": "Password",
|
||||
"in_list_view": 1,
|
||||
"label": "Client Secret"
|
||||
},
|
||||
{
|
||||
"fieldname": "account_id",
|
||||
"fieldtype": "Data",
|
||||
"label": "Account ID"
|
||||
}
|
||||
],
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-03-01 17:15:59.722497",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "Zoom Settings",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2023, Frappe and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class ZoomSettings(Document):
|
||||
pass
|
||||
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"attach_print": 0,
|
||||
"channel": "Email",
|
||||
"creation": "2023-03-27 16:34:03.505645",
|
||||
"days_in_advance": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Notification",
|
||||
"document_type": "Lesson Assignment",
|
||||
"enabled": 1,
|
||||
"event": "New",
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"message": "<h3> {{ _(\"Assignment Submission\") }}\n\n{% set title = frappe.db.get_value(\"Course Lesson\", doc.lesson, \"title\") %}\n\n<p> {{ _(\"{0} has submitted their assignment for the lesson {1}\").format(doc.member_name, title) }} </p>\n\n <p> {{ _(\" Please evaluate and grade the assignment. \") }} </p>",
|
||||
"modified": "2023-03-27 16:46:44.564007",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "Assignment Submission Notification",
|
||||
"owner": "Administrator",
|
||||
"recipients": [
|
||||
{
|
||||
"receiver_by_document_field": "evaluator"
|
||||
}
|
||||
],
|
||||
"send_system_notification": 0,
|
||||
"send_to_all_assignees": 0,
|
||||
"subject": "Assignment Submission"
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<div style="background-color: #f4f5f6; padding: 1rem;">
|
||||
<div style="background-color: #ffffff; width: 75%; margin: 0 auto; padding: 1rem;">
|
||||
<h3> {{ _("Assignment Submission") }} </h3>
|
||||
{% set title = frappe.db.get_value("Course Lesson", doc.lesson, "title") %}
|
||||
<br>
|
||||
<p> {{ _("{0} has submitted their assignment for the lesson {1}").format(frappe.bold(doc.member_name), frappe.bold(title)) }}
|
||||
</p>
|
||||
<p> {{ _(" Please evaluate and grade the assignment.") }} </p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
"event": "New",
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"message": "{% set title = frappe.db.get_value(\"LMS Course\", doc.course, \"title\") %}\n\n<p> {{ _(\"Hey {0}\").format(doc.member_name) }} </p>\n<p> {{ _('Your evaluation for the course {0} has been scheduled on {1} at {2}.').format(title, frappe.utils.format_date(doc.date, \"medium\"), frappe.utils.format_time(doc.start_time, \"short\")) }}</p>\n<p> {{ _(\"Please prepare well and be on time for the evaluations.\") }} </p>\n",
|
||||
"modified": "2023-02-28 19:53:47.716135",
|
||||
"message": "{% set title = frappe.db.get_value(\"LMS Course\", doc.course, \"title\") %}\n<p> {{ _('Your evaluation for the course ${0} has been scheduled on ${1} at ${2}.').format(title, frappe.utils.format_date(doc.date, \"medium\"), frappe.utils.format_time(doc.start_time, \"short\")) }}</p>\n<p> {{ _(\"Please prepare well and be on time for the evaluations.\") }} </p>\n",
|
||||
"modified": "2022-06-03 11:49:01.310656",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "Certificate Request Creation",
|
||||
@@ -19,12 +19,9 @@
|
||||
"recipients": [
|
||||
{
|
||||
"receiver_by_document_field": "member"
|
||||
},
|
||||
{
|
||||
"receiver_by_document_field": "evaluator"
|
||||
}
|
||||
],
|
||||
"send_system_notification": 0,
|
||||
"send_to_all_assignees": 0,
|
||||
"subject": "Your evaluation slot has been booked"
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"absolute_value": 0,
|
||||
"align_labels_right": 0,
|
||||
"creation": "2023-02-22 21:36:54.560420",
|
||||
"css": ".outer-border {\n font-family: \"Inter\" sans-serif;\n font-size: 16px;\n border-radius: 0.5rem;\n border: 1px solid #E2E6E9;\n padding: 1rem;\n}\n\n.inner-border {\n border: 10px solid #0089FF;\n border-radius: 8px;\n text-align: center;\n padding: 6rem 4rem;\n background-color: #FFFFFF;\n}\n\n.certificate-logo {\n height: 1.5rem;\n margin-bottom: 4rem;\n}\n\n.certificate-name {\n font-size: 2rem;\n font-weight: 500;\n color: #192734;\n margin-bottom: 0.5rem;\n}\n\n.certificate-footer {\n margin: 4rem auto 0;\n width: 70%;\n text-align: center;\n}\n\n.certificate-footer-item {\n color: #192734;\n}\n\n.cursive-font {\n font-family: cursive;\n font-weight: 600;\n}\n\n.certificate-divider {\n margin: 0.5rem 0;\n}\n\n.certificate-expiry {\n margin-left: 2rem;\n}",
|
||||
"custom_format": 1,
|
||||
"disabled": 0,
|
||||
"doc_type": "LMS Certificate",
|
||||
"docstatus": 0,
|
||||
"doctype": "Print Format",
|
||||
"font_size": 14,
|
||||
"format_data": "{\"header\":\"<div class=\\\"document-header\\\">\\n\\t<h3>LMS Certificate</h3>\\n\\t<p>{{ doc.name }}</p>\\n</div>\",\"sections\":[{\"label\":\"\",\"columns\":[{\"label\":\"\",\"fields\":[{\"label\":\"Course\",\"fieldname\":\"course\",\"fieldtype\":\"Link\",\"options\":\"LMS Course\"},{\"label\":\"Member\",\"fieldname\":\"member\",\"fieldtype\":\"Link\",\"options\":\"User\"},{\"label\":\"Member Name\",\"fieldname\":\"member_name\",\"fieldtype\":\"Data\"},{\"label\":\"Evaluator\",\"fieldname\":\"evaluator\",\"fieldtype\":\"Data\",\"options\":\"\"}]},{\"label\":\"\",\"fields\":[{\"label\":\"Issue Date\",\"fieldname\":\"issue_date\",\"fieldtype\":\"Date\"},{\"label\":\"Expiry Date\",\"fieldname\":\"expiry_date\",\"fieldtype\":\"Date\"},{\"label\":\"Version\",\"fieldname\":\"version\",\"fieldtype\":\"Select\",\"options\":\"V13\\nV14\"},{\"label\":\"Module Names for Certificate\",\"fieldname\":\"module_names_for_certificate\",\"fieldtype\":\"Data\"}]}],\"has_fields\":true}]}",
|
||||
"html": "{% set certificate = frappe.db.get_value(\"LMS Certificate\", doc.name, [\"name\", \"member\", \"issue_date\", \"expiry_date\", \"course\"], as_dict=True) %}\n{% set member = frappe.db.get_value(\"User\", doc.member, [\"full_name\"], as_dict=True) %}\n{% set course = frappe.db.get_value(\"LMS Course\", doc.course, [\"title\", \"name\", \"image\"], as_dict=True) %}\n{% set logo = frappe.db.get_single_value(\"Website Settings\", \"banner_image\") %}\n{% set instructors = frappe.get_all(\"Course Instructor\", {\"parent\": doc.course}, pluck=\"instructor\", order_by=\"idx\") %}\n\n<meta name=\"pdfkit-orientation\" content=\"Landscape\">\n<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n<link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500&display=swap\" rel=\"stylesheet\">\n\n<div class=\"outer-border\">\n <div class=\"inner-border\">\n \n <img src=\"{{ logo }}\" class=\"certificate-logo\">\n <div>\n {{ _(\"This certifies that\") }}\n </div>\n \n <div class=\"certificate-name\" style=\"\">\n {{ member.full_name }}\n </div>\n <div>\n {{ _(\"has successfully completed the course on\") }}\n <b> {{ course.title }} </b>\n on {{ frappe.utils.format_date(certificate.issue_date, \"medium\") }}.\n </div>\n \n <table class=\"certificate-footer\">\n <tr>\n {% if instructors %}\n <td>\n <div class=\"certificate-footer-item cursive-font\">\n {% for i in instructors %}\n \t\t\t\t\t{{ frappe.db.get_value(\"User\", i, \"full_name\") }}\n \t\t\t\t\t{% if not loop.last %}\n \t\t\t\t\t,\n \t\t\t\t\t{% endif %}\n \t\t\t\t\t{% endfor %}\n </div>\n <hr class=\"certificate-divider\">\n <div class=\"text-center\"> {{ _(\"Course Instructor\") }} </div>\n </td>\n {% endif %}\n \n {% if certificate.expiry_date %}\n <td style=\"width: 30%\"></td>\n \n <td class=\"certificate-expiry\">\n <div class=\"certificate-footer-item\">\n {{ frappe.utils.format_date(certificate.expiry_date, \"medium\") }}\n </div>\n <hr class=\"certificate-divider\">\n <div class=\"text-center\"> {{ _(\"Expiry Date\") }} </div>\n </td>\n {% endif %}\n </tr>\n </table>\n </div>\n </div>",
|
||||
"idx": 0,
|
||||
"line_breaks": 0,
|
||||
"margin_bottom": 0.0,
|
||||
"margin_left": 0.0,
|
||||
"margin_right": 0.0,
|
||||
"margin_top": 0.0,
|
||||
"modified": "2023-04-17 13:46:38.633751",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "Certificate",
|
||||
"owner": "Administrator",
|
||||
"page_number": "Hide",
|
||||
"print_format_builder": 0,
|
||||
"print_format_builder_beta": 1,
|
||||
"print_format_type": "Jinja",
|
||||
"raw_printing": 0,
|
||||
"show_section_headings": 0,
|
||||
"standard": "Yes"
|
||||
}
|
||||
@@ -93,24 +93,18 @@ def get_chapters(course):
|
||||
return chapters
|
||||
|
||||
|
||||
def get_lessons(course, chapter=None, get_details=True):
|
||||
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 = []
|
||||
lesson_count = 0
|
||||
if chapter:
|
||||
if get_details:
|
||||
return get_lesson_details(chapter)
|
||||
else:
|
||||
return frappe.db.count("Lesson Reference", {"parent": chapter.name})
|
||||
return get_lesson_details(chapter)
|
||||
|
||||
for chapter in get_chapters(course):
|
||||
if get_details:
|
||||
lessons += get_lesson_details(chapter)
|
||||
else:
|
||||
lesson_count += frappe.db.count("Lesson Reference", {"parent": chapter.name})
|
||||
lesson = get_lesson_details(chapter)
|
||||
lessons += lesson
|
||||
|
||||
return lessons if get_details else lesson_count
|
||||
return lessons
|
||||
|
||||
|
||||
def get_lesson_details(chapter):
|
||||
@@ -141,8 +135,8 @@ def get_lesson_details(chapter):
|
||||
macros = find_macros(lesson_details.body)
|
||||
|
||||
for macro in macros:
|
||||
if macro[0] == "YouTubeVideo" or macro[0] == "Video":
|
||||
lesson_details.icon = "icon-youtube"
|
||||
if macro[0] == "YouTubeVideo":
|
||||
lesson_details.icon = "icon-video"
|
||||
elif macro[0] == "Quiz":
|
||||
lesson_details.icon = "icon-quiz"
|
||||
lessons.append(lesson_details)
|
||||
@@ -501,17 +495,12 @@ def can_create_courses(member=None):
|
||||
if not member:
|
||||
member = frappe.session.user
|
||||
|
||||
if frappe.session.user == "Guest":
|
||||
return False
|
||||
|
||||
if has_course_instructor_role(member) or has_course_moderator_role(member):
|
||||
return True
|
||||
|
||||
portal_course_creation = frappe.db.get_single_value(
|
||||
"LMS Settings", "portal_course_creation"
|
||||
)
|
||||
|
||||
return portal_course_creation == "Anyone"
|
||||
return frappe.session.user != "Guest" and (
|
||||
portal_course_creation == "Anyone" or has_course_instructor_role(member)
|
||||
)
|
||||
|
||||
|
||||
def has_course_moderator_role(member=None):
|
||||
@@ -622,17 +611,15 @@ def get_filtered_membership(course, memberships):
|
||||
|
||||
|
||||
def show_start_learing_cta(course, membership):
|
||||
|
||||
if course.disable_self_learning or course.upcoming:
|
||||
return False
|
||||
if is_instructor(course.name):
|
||||
return False
|
||||
if course.status != "Approved":
|
||||
return False
|
||||
if not has_lessons(course):
|
||||
return False
|
||||
if not membership:
|
||||
return True
|
||||
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"
|
||||
and has_lessons(course)
|
||||
)
|
||||
|
||||
|
||||
def has_lessons(course):
|
||||
@@ -698,19 +685,3 @@ def get_course_completion_data():
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def get_telemetry_boot_info():
|
||||
POSTHOG_PROJECT_FIELD = "posthog_project_id"
|
||||
POSTHOG_HOST_FIELD = "posthog_host"
|
||||
|
||||
if not frappe.conf.get(POSTHOG_HOST_FIELD) or not frappe.conf.get(
|
||||
POSTHOG_PROJECT_FIELD
|
||||
):
|
||||
return {}
|
||||
|
||||
return {
|
||||
"posthog_host": frappe.conf.get(POSTHOG_HOST_FIELD),
|
||||
"posthog_project_id": frappe.conf.get(POSTHOG_PROJECT_FIELD),
|
||||
"enable_telemetry": 1,
|
||||
}
|
||||
|
||||
3
lms/lms/web_form/class/class.js
Normal file
3
lms/lms/web_form/class/class.js
Normal file
@@ -0,0 +1,3 @@
|
||||
frappe.ready(function () {
|
||||
// bind events here
|
||||
});
|
||||
87
lms/lms/web_form/class/class.json
Normal file
87
lms/lms/web_form/class/class.json
Normal file
@@ -0,0 +1,87 @@
|
||||
{
|
||||
"accept_payment": 0,
|
||||
"allow_comments": 0,
|
||||
"allow_delete": 0,
|
||||
"allow_edit": 0,
|
||||
"allow_incomplete": 0,
|
||||
"allow_multiple": 0,
|
||||
"allow_print": 0,
|
||||
"amount": 0.0,
|
||||
"amount_based_on_field": 0,
|
||||
"apply_document_permissions": 0,
|
||||
"button_label": "Save",
|
||||
"creation": "2022-11-11 12:10:29.640675",
|
||||
"custom_css": "",
|
||||
"doc_type": "LMS Class",
|
||||
"docstatus": 0,
|
||||
"doctype": "Web Form",
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"list_columns": [],
|
||||
"login_required": 0,
|
||||
"max_attachment_size": 0,
|
||||
"modified": "2022-11-21 10:56:01.627821",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "class",
|
||||
"owner": "Administrator",
|
||||
"payment_button_label": "Buy Now",
|
||||
"published": 1,
|
||||
"route": "class",
|
||||
"show_attachments": 0,
|
||||
"show_list": 0,
|
||||
"show_sidebar": 0,
|
||||
"success_title": "",
|
||||
"success_url": "/classes",
|
||||
"title": "Class",
|
||||
"web_form_fields": [
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "Title",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 1,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "start_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"label": "Start Date",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 1,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "end_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"label": "End Date",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 1,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 0,
|
||||
"label": "Description",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
frappe.ready(function () {
|
||||
frappe.web_form.after_save = () => {
|
||||
let data = frappe.web_form.get_values();
|
||||
if (data.class) {
|
||||
setTimeout(() => {
|
||||
window.location.href = `/classes/${data.class}`;
|
||||
}, 2000);
|
||||
}
|
||||
setTimeout(() => {
|
||||
window.history.back();
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"list_columns": [],
|
||||
"login_required": 1,
|
||||
"max_attachment_size": 0,
|
||||
"modified": "2023-02-23 13:04:00.405266",
|
||||
"modified": "2022-11-25 17:05:30.851109",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "evaluation",
|
||||
@@ -29,7 +29,7 @@
|
||||
"published": 1,
|
||||
"route": "evaluation",
|
||||
"show_attachments": 0,
|
||||
"show_list": 1,
|
||||
"show_list": 0,
|
||||
"show_sidebar": 0,
|
||||
"title": "Evaluation",
|
||||
"web_form_fields": [
|
||||
@@ -59,31 +59,6 @@
|
||||
"reqd": 1,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "summary",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 0,
|
||||
"label": "Summary",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"label": "",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "date",
|
||||
@@ -120,6 +95,19 @@
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"label": "",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "rating",
|
||||
@@ -147,16 +135,15 @@
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "class",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"label": "Class",
|
||||
"fieldname": "summary",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 0,
|
||||
"label": "Summary",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "LMS Class",
|
||||
"read_only": 1,
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"button_label": "Save",
|
||||
"client_script": "",
|
||||
"creation": "2021-06-30 13:48:13.682851",
|
||||
"custom_css": "",
|
||||
"custom_css": "[data-doctype=\"Web Form\"] {\n max-width: 720px;\n margin: 6rem auto;\n}",
|
||||
"doc_type": "User",
|
||||
"docstatus": 0,
|
||||
"doctype": "Web Form",
|
||||
@@ -22,7 +22,7 @@
|
||||
"list_columns": [],
|
||||
"login_required": 1,
|
||||
"max_attachment_size": 0,
|
||||
"modified": "2023-01-09 15:45:11.411692",
|
||||
"modified": "2022-09-05 13:08:40.071348",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "profile",
|
||||
@@ -100,10 +100,22 @@
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "city",
|
||||
"fieldname": "headline",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "City",
|
||||
"label": "Headline",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "bio",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 0,
|
||||
"label": "Bio",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
@@ -123,31 +135,6 @@
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"label": "",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "headline",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "Headline",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "linkedin",
|
||||
@@ -186,10 +173,10 @@
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "looking_for_job",
|
||||
"fieldtype": "Check",
|
||||
"fieldname": "city",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "I am looking for a job",
|
||||
"label": "City",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
@@ -198,38 +185,12 @@
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "bio",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 0,
|
||||
"label": "Bio",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "",
|
||||
"fieldtype": "Page Break",
|
||||
"hidden": 0,
|
||||
"label": "",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "",
|
||||
"fieldname": "education_details",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"label": "",
|
||||
"label": "Education Details",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
@@ -336,6 +297,187 @@
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "carrer_preference_details",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"label": "Career Preference",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "preferred_functions",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"label": "Preferred Functions",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "Preferred Function",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "preferred_industries",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"label": "Preferred Industries",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "Preferred Industry",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "preferred_location",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "Preferred Locations",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "dream_companies",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "Dream Companies",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "work_environment",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"label": "Work Environment",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "attire",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"label": "Attire Preference",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "Casual Wear\nFormal Wear",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "collaboration",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"label": "Collaboration Preference",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "Individual Work\nTeam Work\nBoth Individual and Team Work",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "role",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"label": "Role Preference",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "Clearly Defined Role\nUnstructured Role",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "location_preference",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"label": "Location Preference",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "Travel\nOffice close to Home",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "time",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"label": "Time Preference",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "Flexible Time\nFixed 9-5",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "company_type",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"label": "Company Type",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "Corporate Organization\nStartup Organization",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "looking_for_job",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"label": "I am looking for a job",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"description": "Private Information includes your Mobile Number, Email Address, Grade Type, Grade and Work Environment Preferences",
|
||||
"fieldname": "hide_private",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"label": "Hide my Private Information from others",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,66 +1,78 @@
|
||||
{% set chapters = get_chapters(course.name) %}
|
||||
{% set is_instructor = is_instructor(course.name) %}
|
||||
|
||||
{% if chapters | length %}
|
||||
{% if course.edit_mode or chapters | length %}
|
||||
<div class="course-home-outline">
|
||||
|
||||
{% if not lesson_page %}
|
||||
<div class="page-title mb-8" id="outline-heading" data-course="{{ course.name }}">
|
||||
|
||||
{% if course.edit_mode and course.name %}
|
||||
<button class="btn btn-md btn-secondary btn-chapter pull-right"> {{ _("New Chapter") }} </button>
|
||||
{% endif %}
|
||||
|
||||
{% if course.name and (course.edit_mode or chapters | length) %}
|
||||
<div class="course-home-headings" id="outline-heading">
|
||||
{{ _("Course Content") }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- <div class="mb-2">
|
||||
<span>
|
||||
{{ chapters | length }} chapters
|
||||
</span>
|
||||
<span>
|
||||
. {{ get_lessons(course.name, None, False) }} lessons
|
||||
</span>
|
||||
</div> -->
|
||||
{% if course.edit_mode and course.name and not chapters | length %}
|
||||
<div class="chapter-parent chapter-edit new-chapter">
|
||||
<div contenteditable="true" data-placeholder="{{ _('Chapter Name') }}" class="chapter-title-main"></div>
|
||||
<div class="chapter-description small my-2" contenteditable="true" data-placeholder="{{ _('Short Description') }}"></div>
|
||||
<button class="btn btn-sm btn-secondary d-block btn-save-chapter" data-index="1"> {{ _('Save') }} </button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if chapters | length %}
|
||||
<div>
|
||||
|
||||
{% for chapter in chapters %}
|
||||
{% set lessons = get_lessons(course.name, chapter) %}
|
||||
|
||||
<div class="chapter-parent" data-chapter="{{ chapter.name }}">
|
||||
|
||||
<div class="chapter-title" data-toggle="collapse" aria-expanded="false"
|
||||
data-target="#{{ get_slugified_chapter_title(chapter.title) }}">
|
||||
|
||||
<div class="chapter-parent {% if course.edit_mode %} chapter-edit {% endif %} ">
|
||||
<div class="chapter-title" {% if not course.edit_mode %} data-toggle="collapse" aria-expanded="false"
|
||||
data-target="#{{ get_slugified_chapter_title(chapter.title) }}" {% endif %} >
|
||||
{% if not course.edit_mode %}
|
||||
<img class="chapter-icon" src="/assets/lms/icons/chevron-right.svg">
|
||||
<div class="chapter-title-main">
|
||||
{{ chapter.title }}
|
||||
</div>
|
||||
<!-- <div class="small ml-auto">
|
||||
{{ lessons | length }} lessons
|
||||
</div> -->
|
||||
|
||||
{% endif %}
|
||||
<div class="w-100 chapter-title-main" {% if course.edit_mode %} contenteditable="true" {% endif %} >{{ chapter.title }}</div>
|
||||
</div>
|
||||
|
||||
{% set lessons = get_lessons(course.name, chapter) %}
|
||||
|
||||
<div class="chapter-content collapse navbar-collapse" id="{{ get_slugified_chapter_title(chapter.title) }}">
|
||||
<div class="chapter-content {% if not course.edit_mode %} collapse navbar-collapse {% endif %} "
|
||||
id="{{ get_slugified_chapter_title(chapter.title) }}">
|
||||
|
||||
{% if chapter.description %}
|
||||
<div class="chapter-description">
|
||||
{{ chapter.description }}
|
||||
{% if chapter.description or course.edit_mode %}
|
||||
<div {% if course.edit_mode %} contenteditable="true" {% endif %} class="chapter-description
|
||||
{% if not course.edit_mode %} mx-8 mb-2 {% endif %} "
|
||||
data-placeholder="{{ _('Short Description') }}">{{ chapter.description }}</div>
|
||||
{% endif %}
|
||||
|
||||
{% if course.edit_mode %}
|
||||
<div class="mt-2">
|
||||
<button class="btn btn-sm btn-secondary btn-save-chapter"
|
||||
data-index="{{ loop.index }}" data-chapter="{{ chapter.name }}"> {{ _('Save') }} </button>
|
||||
<a class="btn btn-sm btn-secondary btn-lesson ml-2"
|
||||
href="/courses/{{ course.name }}/learn/{{loop.index}}.{{ lessons | length + 1 }}?edit=1"> {{ _("New Lesson") }} </a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% set is_instructor = is_instructor(course.name) %}
|
||||
|
||||
{% if lessons | length %}
|
||||
<div class="lessons">
|
||||
|
||||
{% if lessons | length %}
|
||||
{% if course.edit_mode %}
|
||||
<b class="course-meta"> {{ _("Lessons") }}: </b>
|
||||
{% endif %}
|
||||
|
||||
{% for lesson in lessons %}
|
||||
{% set active = membership.current_lesson == lesson.name %}
|
||||
<div data-lesson="{{ lesson.name }}" class="lesson-info {% if active %} active-lesson {% endif %}">
|
||||
<div class="lesson-info {% if active and not course.edit_mode %} active-lesson {% endif %}">
|
||||
|
||||
{% if membership or lesson.include_in_preview or is_instructor or has_course_moderator_role() %}
|
||||
<a class="lesson-links" href="{{ get_lesson_url(course.name, lesson.number) }}{{course.query_parameter}}"
|
||||
<a class="lesson-links" data-course="{{ course.name }}"
|
||||
{% if is_instructor and not lesson.include_in_preview %}
|
||||
title="{{ _('This lesson is not available for preview. As you are the Instructor of the course only you can see it.') }}"
|
||||
{% endif %}>
|
||||
{% endif %}
|
||||
href="{{ get_lesson_url(course.name, lesson.number) }}{% if course.edit_mode and is_instructor %}?edit=1{% endif %}{{course.query_parameter}}">
|
||||
|
||||
<svg class="icon icon-sm mr-2">
|
||||
<use class="" href="#{{ lesson.icon }}">
|
||||
@@ -69,15 +81,15 @@
|
||||
<span>{{ lesson.title }}</span>
|
||||
|
||||
{% if membership %}
|
||||
<svg class="icon icon-md lesson-progress-tick ml-auto {{ get_progress(course.name, lesson.name) != 'Complete' and 'hide' }}">
|
||||
<use class="" href="#icon-success">
|
||||
<svg class="icon icon-sm lesson-progress-tick {{ get_progress(course.name, lesson.name) != 'Complete' and 'hide' }}">
|
||||
<use class="" href="#icon-green-check">
|
||||
</svg>
|
||||
{% endif %}
|
||||
|
||||
</a>
|
||||
|
||||
{% else %}
|
||||
<div class="no-preview" title="This lesson is not available for preview">
|
||||
<div class="no-preview" title="This lesson is not available for preview" data-course="{{ course.name }}">
|
||||
<div class="lesson-links">
|
||||
<svg class="icon icon-sm mr-2">
|
||||
<use class="" href="#icon-lock-gray">
|
||||
@@ -90,13 +102,12 @@
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
|
||||
@@ -108,3 +119,87 @@
|
||||
{{ widgets.NoPreviewModal(course=course, membership=membership) }}
|
||||
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
|
||||
frappe.ready(() => {
|
||||
|
||||
expand_the_active_chapter();
|
||||
|
||||
$(".chapter-title").unbind().click((e) => {
|
||||
rotate_chapter_icon(e);
|
||||
});
|
||||
|
||||
$(".no-preview").click((e) => {
|
||||
show_no_preview_dialog(e);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
const expand_the_first_chapter = () => {
|
||||
let elements = $(".course-home-outline .collapse");
|
||||
elements.each((i, element) => {
|
||||
if (i < 1) {
|
||||
show_section(element);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const expand_the_active_chapter = () => {
|
||||
|
||||
/* Find anchor matching the URL for course details page */
|
||||
let selector = $(`a[href="${decodeURIComponent(window.location.pathname)}"]`).parent();
|
||||
if (!selector.length) {
|
||||
selector = $(`a[href^="${decodeURIComponent(window.location.pathname)}"]`).parent();
|
||||
}
|
||||
if (selector.length && $(".course-details-page").length) {
|
||||
$(".lesson-info").removeClass("active-lesson");
|
||||
$(".lesson-info").each((i, elem) => {
|
||||
let href = $(elem).find("use").attr("href");
|
||||
href.endsWith("blue") && $(elem).find("use").attr("href", href.substring(0, href.length - 5));
|
||||
})
|
||||
|
||||
selector.addClass("active-lesson");
|
||||
|
||||
show_section(selector.parent().parent());
|
||||
}
|
||||
|
||||
/* For course home page */
|
||||
else if ($(".active-lesson").length) {
|
||||
selector = $(".active-lesson")
|
||||
show_section(selector.parent().parent());
|
||||
}
|
||||
|
||||
/* If no active chapter then exapand the first chapter */
|
||||
else {
|
||||
expand_the_first_chapter();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const show_section = (element) => {
|
||||
$(element).addClass("show");
|
||||
$(element).siblings(".chapter-title").children(".chapter-icon").css("transform", "rotate(90deg)");
|
||||
$(element).siblings(".chapter-title").attr("aria-expanded", true);
|
||||
};
|
||||
|
||||
|
||||
const rotate_chapter_icon = (e) => {
|
||||
let icon = $(e.currentTarget).children(".chapter-icon");
|
||||
if (icon.css("transform") == "none") {
|
||||
icon.css("transform", "rotate(90deg)");
|
||||
} else {
|
||||
icon.css("transform", "none");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const show_no_preview_dialog = (e) => {
|
||||
$("#no-preview-modal").modal("show");
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,31 +1,19 @@
|
||||
{% set color = get_palette(member.full_name) %}
|
||||
<div class="common-card-style member-card">
|
||||
<div class="d-flex">
|
||||
{{ widgets.Avatar(member=member, avatar_class=avatar_class) }}
|
||||
{{ widgets.Avatar(member=member, avatar_class=avatar_class) }}
|
||||
<div class="bold-title mt-4">
|
||||
{{ member.full_name }}
|
||||
</div>
|
||||
|
||||
<div class="ml-3 my-auto">
|
||||
<div class="member-card-title">
|
||||
{{ member.full_name }}
|
||||
</div>
|
||||
|
||||
{% if member.headline %}
|
||||
<div> {{ member.headline }} </div>
|
||||
{% endif %}
|
||||
|
||||
{% if member.looking_for_job %}
|
||||
<div class="indicator-pill green"> {{ _("Open Network") }} </div>
|
||||
{% endif %}
|
||||
|
||||
{% set course_count = get_authored_courses(member.name, True) | length %}
|
||||
{% set suffix = "Courses" if course_count > 1 else "Course" %}
|
||||
|
||||
{% if show_course_count and course_count > 0 %}
|
||||
<div class="">
|
||||
Created {{ course_count }} {{ suffix }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<a class="stretched-link" href="{{ get_profile_url(member.username) }}"></a>
|
||||
</div>
|
||||
{% if member.headline %}
|
||||
<div> {{ member.headline }} </div>
|
||||
{% endif %}
|
||||
|
||||
{% set course_count = get_authored_courses(member.name, True) | length %}
|
||||
{% if show_course_count and course_count > 0 %}
|
||||
{% set suffix = "Courses" if course_count > 1 else "Course" %}
|
||||
<div class="">
|
||||
Created {{ course_count }} {{ suffix }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<a class="stretched-link" href="{{ get_profile_url(member.username) }}"></a>
|
||||
</div>
|
||||
|
||||
@@ -9,14 +9,13 @@
|
||||
"label": "Enrollments"
|
||||
}
|
||||
],
|
||||
"content": "[{\"id\":\"jNO4sdKxHu\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Get Started</b></span>\",\"col\":12}},{\"id\":\"5s0qRBc4rY\",\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"/courses\\\" draggable=\\\"false\\\">Visit LMS Portal</a>\",\"col\":4}},{\"id\":\"lGMuNLpmv-\",\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"/courses/new-course/edit\\\">Create a Course</a>\",\"col\":4}},{\"id\":\"3TVyc9AkPy\",\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"/app/web-page/new-web-page-1\\\">Setup a Home Page</a>\",\"col\":4}},{\"id\":\"9zcbqpu2gm\",\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"/app/lms-settings/LMS%20Settings\\\">LMS Setting</a>\",\"col\":4}},{\"id\":\"0ATmnKmXjc\",\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"https://docs.frappelms.com\\\">Documentation</a>\",\"col\":4}},{\"id\":\"7tGB2TYPmn\",\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"https://frappe.school/courses/introducing-frappe-lms\\\">Video Tutorials</a>\",\"col\":4}},{\"id\":\"C128a4abjX\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"5q4sPiv2ci\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Signups\",\"col\":6}},{\"id\":\"8NSaRaEV5u\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Enrollments\",\"col\":6}},{\"id\":\"kMuzko0uAU\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"iuvIOHmztI\",\"type\":\"header\",\"data\":{\"text\":\"<span style=\\\"font-size: 18px;\\\"><b>Statistics</b></span>\",\"col\":12}},{\"id\":\"l0VTd66Uy2\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Users\",\"col\":4}},{\"id\":\"wAWZin1KKk\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Course\",\"col\":4}},{\"id\":\"RLrIlFx0Hd\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Enrollments\",\"col\":4}},{\"id\":\"OuhWkhCQmq\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Course Completed\",\"col\":4}},{\"id\":\"3g8QmNqUXG\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Certificate\",\"col\":4}},{\"id\":\"EZsdsujs8N\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Evaluation\",\"col\":4}},{\"id\":\"s-nfsFQbGV\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"jeOBWBzHEa\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Master</b></span>\",\"col\":12}},{\"id\":\"sVhgfS5GIh\",\"type\":\"card\",\"data\":{\"card_name\":\"Course Data\",\"col\":4}},{\"id\":\"Iea0snm4Fg\",\"type\":\"card\",\"data\":{\"card_name\":\"Course Stats\",\"col\":4}},{\"id\":\"bZB7RqOl6a\",\"type\":\"card\",\"data\":{\"card_name\":\"Certification\",\"col\":4}}]",
|
||||
"content": "[{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Get Started</b></span>\",\"col\":12}},{\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"/courses\\\" draggable=\\\"false\\\">Visit LMS Portal</a>\",\"col\":4}},{\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"/courses/new-course\\\" draggable=\\\"false\\\">Create a Course</a>\",\"col\":4}},{\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"/app/web-page/new-web-page-1\\\">Setup a Home Page</a>\",\"col\":4}},{\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"/app/website-settings/Website%20Settings\\\">Website Settings</a>\",\"col\":4}},{\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"https://docs.frappelms.com\\\">Documentation</a>\",\"col\":4}},{\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"https://frappe.school/courses/introducing-frappe-lms\\\">Video Tutorials</a>\",\"col\":4}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Signups\",\"col\":6}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Enrollments\",\"col\":6}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span style=\\\"font-size: 18px;\\\"><b>Statistics</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Users\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Course\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Enrollments\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Course Completed\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Certificate\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Evaluation\",\"col\":4}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Master</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Course Data\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Course Stats\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Certification\",\"col\":4}}]",
|
||||
"creation": "2021-10-21 17:20:01.358903",
|
||||
"docstatus": 0,
|
||||
"doctype": "Workspace",
|
||||
"hide_custom": 0,
|
||||
"icon": "education",
|
||||
"idx": 0,
|
||||
"is_hidden": 0,
|
||||
"label": "LMS",
|
||||
"links": [
|
||||
{
|
||||
@@ -144,11 +143,10 @@
|
||||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"modified": "2023-05-11 15:41:25.514442",
|
||||
"modified": "2022-12-28 17:45:18.539185",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS",
|
||||
"number_cards": [],
|
||||
"owner": "Administrator",
|
||||
"parent_page": "",
|
||||
"public": 1,
|
||||
|
||||
@@ -20,6 +20,14 @@ class TestCustomUser(unittest.TestCase):
|
||||
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_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)
|
||||
@@ -29,6 +37,8 @@ class TestCustomUser(unittest.TestCase):
|
||||
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]})
|
||||
|
||||
@@ -9,29 +9,64 @@ 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 frappe.website.utils import cleanup_page_name
|
||||
from frappe.model.naming import append_number_if_name_exists
|
||||
from lms.widgets import Widgets
|
||||
|
||||
|
||||
class CustomUser(User):
|
||||
def validate(self):
|
||||
super().validate()
|
||||
self.validate_username_duplicates()
|
||||
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_duplicates(self):
|
||||
while not self.username or self.username_exists():
|
||||
self.username = append_number_if_name_exists(
|
||||
self.doctype, cleanup_page_name(self.full_name), fieldname="username"
|
||||
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
|
||||
)
|
||||
if " " in self.username:
|
||||
self.username = self.username.replace(" ", "")
|
||||
else:
|
||||
other_conditions = ""
|
||||
|
||||
if len(self.username) < 4:
|
||||
self.username = self.email.replace("@", "").replace(".", "")
|
||||
regex = re.compile(r"[@!#$%^&*()<>?/\|}{~:-]")
|
||||
|
||||
if self.is_new():
|
||||
if not self.username:
|
||||
self.username = self.get_username_from_first_name()
|
||||
|
||||
if self.username.find(" "):
|
||||
self.username.replace(" ", "")
|
||||
|
||||
if len(self.username) < 4:
|
||||
self.username = self.email.replace("@", "").replace(".", "")
|
||||
|
||||
if regex.search(self.username) or other_conditions:
|
||||
self.username = self.remove_illegal_characters()
|
||||
|
||||
while self.username_exists():
|
||||
self.username = self.remove_illegal_characters() + str(random.randint(0, 99))
|
||||
|
||||
else:
|
||||
if not self.username:
|
||||
frappe.throw(_("Username already exists."))
|
||||
|
||||
if regex.search(self.username):
|
||||
frappe.throw(_("Username can only contain alphabets, numbers and underscore."))
|
||||
|
||||
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(_)."))
|
||||
|
||||
if len(self.username) < 4:
|
||||
frappe.throw(_("Username cannot be less than 4 characters"))
|
||||
|
||||
def get_username_from_first_name(self):
|
||||
return frappe.scrub(self.first_name) + str(random.randint(0, 99))
|
||||
|
||||
def remove_illegal_characters(self):
|
||||
return re.sub(r"[^\w]+", "", self.username).strip("_")
|
||||
|
||||
def validate_skills(self):
|
||||
unique_skills = []
|
||||
@@ -238,6 +273,7 @@ def sign_up(email, full_name, verify_terms, user_category):
|
||||
def set_country_from_ip(login_manager=None, user=None):
|
||||
if not user and login_manager:
|
||||
user = login_manager.user
|
||||
|
||||
user_country = frappe.db.get_value("User", user, "country")
|
||||
# if user_country:
|
||||
# return
|
||||
@@ -258,14 +294,16 @@ def get_country_code():
|
||||
return
|
||||
|
||||
|
||||
def on_login(login_manager):
|
||||
set_country_from_ip()
|
||||
|
||||
|
||||
def on_session_creation(login_manager):
|
||||
if frappe.db.get_single_value(
|
||||
"System Settings", "setup_complete"
|
||||
) and frappe.db.get_single_value("LMS Settings", "default_home"):
|
||||
if frappe.db.get_single_value("System Settings", "setup_complete"):
|
||||
frappe.local.response["home_page"] = "/courses"
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@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))
|
||||
@@ -312,7 +350,7 @@ def get_user_details(users):
|
||||
details = frappe.db.get_value(
|
||||
"User",
|
||||
user,
|
||||
["name", "username", "full_name", "user_image", "headline", "looking_for_job"],
|
||||
["name", "username", "full_name", "user_image", "headline"],
|
||||
as_dict=True,
|
||||
)
|
||||
user_details.append(Widgets().MemberCard(member=details, avatar_class="avatar-large"))
|
||||
|
||||
@@ -57,8 +57,8 @@ class ProfilePage(BaseRenderer):
|
||||
self.renderer = None
|
||||
|
||||
def can_render(self):
|
||||
"""if "." in self.path:
|
||||
return False"""
|
||||
if "." in self.path:
|
||||
return False
|
||||
|
||||
# has prefix and path starts with prefix?
|
||||
prefix = get_profile_url_prefix().lstrip("/")
|
||||
@@ -67,8 +67,8 @@ class ProfilePage(BaseRenderer):
|
||||
|
||||
# not a userpage?
|
||||
username = self.get_username()
|
||||
""" if RE_INVALID_USERNAME.search(username):
|
||||
return False """
|
||||
if RE_INVALID_USERNAME.search(username):
|
||||
return False
|
||||
# if there is prefix then we can allow all usernames
|
||||
if prefix:
|
||||
return True
|
||||
|
||||
@@ -34,7 +34,7 @@ lms.patches.v0_0.create_course_instructor_role #29-08-2022
|
||||
lms.patches.v0_0.create_course_moderator_role
|
||||
lms.patches.v0_0.set_dashboard #11-10-2022
|
||||
lms.patches.v0_0.set_courses_page_as_home
|
||||
lms.patches.v0_0.set_member_in_progress #03-03-2023
|
||||
lms.patches.v0_0.set_member_in_progress #09-11-2022
|
||||
lms.patches.v0_0.convert_progress_to_float
|
||||
lms.patches.v0_0.add_pages_to_nav #25-11-2022
|
||||
lms.patches.v0_0.change_role_names
|
||||
@@ -44,13 +44,3 @@ lms.patches.v0_0.rename_instructor_role
|
||||
lms.patches.v0_0.change_course_creation_settings #12-12-2022
|
||||
lms.patches.v0_0.check_onboarding_status #21-12-2022
|
||||
lms.patches.v0_0.assignment_file_type
|
||||
lms.patches.v0_0.user_singles_issue #23-11-2022
|
||||
lms.patches.v0_0.rename_community_to_users #06-01-2023
|
||||
lms.patches.v0_0.video_embed_link
|
||||
lms.patches.v0_0.rename_exercise_doctype
|
||||
lms.patches.v0_0.add_question_type #09-04-2023
|
||||
lms.patches.v0_0.add_evaluator_to_assignment #09-04-2023
|
||||
lms.patches.v0_0.share_certificates
|
||||
execute:frappe.delete_doc("Web Form", "class", ignore_missing=True, force=True)
|
||||
lms.patches.v0_0.amend_course_and_lesson_editor_fields
|
||||
lms.patches.v0_0.convert_course_description_to_html #11-05-2023
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc("lms", "doctype", "lesson_assignment")
|
||||
assignments = frappe.get_all("Lesson Assignment", fields=["name", "course"])
|
||||
for assignment in assignments:
|
||||
evaluator = frappe.db.get_value("LMS Course", assignment.course, "evaluator")
|
||||
frappe.db.set_value("Lesson Assignment", assignment.name, "evaluator", evaluator)
|
||||
@@ -1,9 +0,0 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc("lms", "doctype", "lms_quiz_question")
|
||||
questions = frappe.get_all("LMS Quiz Question", pluck="name")
|
||||
|
||||
for question in questions:
|
||||
frappe.db.set_value("LMS Quiz Question", question, "type", "Choices")
|
||||
@@ -1,37 +0,0 @@
|
||||
import frappe
|
||||
from frappe.utils import to_markdown
|
||||
|
||||
|
||||
def execute():
|
||||
amend_lesson_content()
|
||||
amend_course_description()
|
||||
|
||||
|
||||
def amend_lesson_content():
|
||||
lesson_content_field = frappe.db.get_value(
|
||||
"DocField", {"parent": "Course Lesson", "fieldname": "body"}, "fieldtype"
|
||||
)
|
||||
|
||||
if lesson_content_field == "Text Editor":
|
||||
lessons = frappe.get_all("Course Lesson", fields=["name", "body"])
|
||||
|
||||
for lesson in lessons:
|
||||
frappe.db.set_value("Course Lesson", lesson.name, "body", to_markdown(lesson.body))
|
||||
|
||||
frappe.reload_doc("lms", "doctype", "course_lesson")
|
||||
|
||||
|
||||
def amend_course_description():
|
||||
course_description_field = frappe.db.get_value(
|
||||
"DocField", {"parent": "LMS Course", "fieldname": "description"}, "fieldtype"
|
||||
)
|
||||
|
||||
if course_description_field == "Text Editor":
|
||||
courses = frappe.get_all("LMS Course", fields=["name", "description"])
|
||||
|
||||
for course in courses:
|
||||
frappe.db.set_value(
|
||||
"LMS Course", course.name, "description", to_markdown(course.description)
|
||||
)
|
||||
|
||||
frappe.reload_doc("lms", "doctype", "lms_course")
|
||||
@@ -1,12 +0,0 @@
|
||||
import frappe
|
||||
from lms.lms.md import markdown_to_html
|
||||
|
||||
|
||||
def execute():
|
||||
courses = frappe.get_all("LMS Course", fields=["name", "description"])
|
||||
|
||||
for course in courses:
|
||||
html = markdown_to_html(course.description)
|
||||
frappe.db.set_value("LMS Course", course.name, "description", html)
|
||||
|
||||
frappe.reload_doc("lms", "doctype", "lms_course")
|
||||
@@ -1,12 +0,0 @@
|
||||
import frappe
|
||||
from lms.lms.md import markdown_to_html
|
||||
|
||||
|
||||
def execute():
|
||||
lessons = frappe.get_all("Course Lesson", fields=["name", "body"])
|
||||
|
||||
for lesson in lessons:
|
||||
html = markdown_to_html(lesson.body)
|
||||
frappe.db.set_value("Course Lesson", lesson.name, "body", html)
|
||||
|
||||
frappe.reload_doc("lms", "doctype", "course_lesson")
|
||||
@@ -1,6 +1,9 @@
|
||||
from venv import create
|
||||
|
||||
import frappe
|
||||
from lms.install import create_course_creator_role
|
||||
|
||||
from lms.install import create_instructor_role
|
||||
|
||||
|
||||
def execute():
|
||||
create_course_creator_role()
|
||||
create_instructor_role()
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
doc = frappe.db.exists("Top Bar Item", {"url": "/community"})
|
||||
if doc:
|
||||
frappe.db.set_value("Top Bar Item", doc, {"url": "/people", "label": "People"})
|
||||
@@ -1,13 +0,0 @@
|
||||
import frappe
|
||||
from frappe.model.rename_doc import rename_doc
|
||||
|
||||
|
||||
def execute():
|
||||
if frappe.db.exists("DocType", "LMS Exercise"):
|
||||
return
|
||||
|
||||
frappe.flags.ignore_route_conflict_validation = True
|
||||
rename_doc("DocType", "Exercise", "LMS Exercise")
|
||||
frappe.flags.ignore_route_conflict_validation = False
|
||||
|
||||
frappe.reload_doctype("LMS Exercise", force=True)
|
||||
@@ -3,12 +3,9 @@ import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc("lms", "doctype", "lms_course_progress")
|
||||
progress_records = frappe.get_all(
|
||||
"LMS Course Progress", fields=["name", "owner", "member"]
|
||||
)
|
||||
progress_records = frappe.get_all("LMS Course Progress", fields=["name", "owner"])
|
||||
|
||||
for progress in progress_records:
|
||||
if not progress.member:
|
||||
full_name = frappe.db.get_value("User", progress.owner, "full_name")
|
||||
frappe.db.set_value("LMS Course Progress", progress.name, "member", progress.owner)
|
||||
frappe.db.set_value("LMS Course Progress", progress.name, "member_name", full_name)
|
||||
full_name = frappe.db.get_value("User", progress.owner, "full_name")
|
||||
frappe.db.set_value("LMS Course Progress", progress.name, "member", progress.owner)
|
||||
frappe.db.set_value("LMS Course Progress", progress.name, "member_name", full_name)
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
certificates = frappe.get_all("LMS Certificate", fields=["member", "name"])
|
||||
|
||||
for certificate in certificates:
|
||||
if not frappe.db.exists(
|
||||
"DocShare",
|
||||
{
|
||||
"share_doctype": "LMS Certificate",
|
||||
"share_name": certificate.name,
|
||||
"user": certificate.member,
|
||||
},
|
||||
):
|
||||
share = frappe.get_doc(
|
||||
{
|
||||
"doctype": "DocShare",
|
||||
"user": certificate.member,
|
||||
"share_doctype": "LMS Certificate",
|
||||
"share_name": certificate.name,
|
||||
"read": 1,
|
||||
}
|
||||
)
|
||||
share.save(ignore_permissions=True)
|
||||
@@ -1,10 +0,0 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
table = frappe.qb.DocType("Singles")
|
||||
q = frappe.qb.from_(table).select(table.field).where(table.doctype == "User")
|
||||
rows = q.run()
|
||||
|
||||
if len(rows):
|
||||
frappe.db.delete("Singles", {"doctype": "User"})
|
||||
@@ -1,11 +0,0 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
courses = frappe.get_all(
|
||||
"LMS Course", {"video_link": ["is", "set"]}, ["name", "video_link"]
|
||||
)
|
||||
for course in courses:
|
||||
if course.video_link:
|
||||
link = course.video_link.split("/")[-1]
|
||||
frappe.db.set_value("LMS Course", course.name, "video_link", link)
|
||||
@@ -119,7 +119,7 @@ def quiz_renderer(quiz_name):
|
||||
|
||||
|
||||
def exercise_renderer(argument):
|
||||
exercise = frappe.get_doc("LMS Exercise", argument)
|
||||
exercise = frappe.get_doc("Exercise", argument)
|
||||
context = dict(exercise=exercise)
|
||||
return frappe.render_template("templates/exercise.html", context)
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="icon-quiz" viewBox="0 0 1024 1024" stroke="#1F272E">
|
||||
<path d="M512 0C229.232 0 0 229.232 0 512c0 282.784 229.232 512 512 512 282.784 0 512.017-229.216 512.017-512C1024.017 229.232 794.785 0 512 0zm0 961.008c-247.024 0-448-201.984-448-449.01 0-247.024 200.976-448 448-448s448.017 200.977 448.017 448S759.025 961.009 512 961.009zm-47.056-160.529h80.512v-81.248h-80.512zm46.112-576.944c-46.88 0-85.503 12.64-115.839 37.889-30.336 25.263-45.088 75.855-44.336 117.775l1.184 2.336h73.44c0-25.008 8.336-60.944 25.008-73.84 16.656-12.88 36.848-19.328 60.56-19.328 27.328 0 48.336 7.424 63.073 22.271 14.72 14.848 22.063 36.08 22.063 63.664 0 23.184-5.44 42.976-16.368 59.376-10.96 16.4-29.328 39.841-55.088 70.322-26.576 23.967-42.992 43.231-49.232 57.807-6.256 14.592-9.504 40.768-9.744 78.512h76.96c0-23.68 1.503-41.136 4.496-52.336 2.975-11.184 11.504-23.823 25.568-37.888 30.224-29.152 54.496-57.664 72.88-85.551 18.336-27.857 27.52-58.593 27.52-92.193 0-46.88-14.176-83.408-42.577-109.568-28.416-26.176-68.272-39.248-119.568-39.248z" fill="#1F272E"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -5,39 +5,9 @@
|
||||
<svg id="icon-video-blue" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1 9C1 4.58172 4.58172 1 9 1C11.1217 1 13.1566 1.84286 14.6569 3.34315C16.1571 4.84343 17 6.87827 17 9C17 13.4182 13.4182 17 9 17C4.58172 17 1 13.4182 1 9ZM8.00636 12.0679L11.8766 9.51133C12.0614 9.40191 12.174 9.2084 12.174 9C12.174 8.79161 12.0614 8.59809 11.8766 8.48867L8.00636 5.932C7.79102 5.78453 7.51 5.75869 7.2694 5.86422C7.0288 5.96977 6.86529 6.1906 6.84063 6.44334V11.5567C6.86529 11.8094 7.0288 12.0302 7.2694 12.1358C7.51 12.2413 7.79102 12.2155 8.00636 12.0679Z" fill="#2D95F0"/>
|
||||
</svg>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" id="icon-youtube" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_3613_984)">
|
||||
<mask id="mask0_3613_984" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="16" height="16">
|
||||
<path d="M16 0H0V16H16V0Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_3613_984)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.77778 12.1111H13.2222C13.7132 12.1111 14.1111 11.7132 14.1111 11.2222V2.77778C14.1111 2.28686 13.7132 1.88889 13.2222 1.88889H2.77778C2.28686 1.88889 1.88889 2.28686 1.88889 2.77778V11.2222C1.88889 11.7132 2.28686 12.1111 2.77778 12.1111ZM13.2222 13C14.2041 13 15 12.2041 15 11.2222V2.77778C15 1.79594 14.2041 1 13.2222 1H2.77778C1.79594 1 1 1.79594 1 2.77778V11.2222C1 12.2041 1.79594 13 2.77778 13H13.2222ZM5.99989 4.76006C5.99989 4.22072 6.60701 3.90461 7.04886 4.21391L10.328 6.50932C10.7072 6.77472 10.7072 7.33622 10.328 7.60163L7.04887 9.89707C6.60701 10.2063 5.99989 9.89022 5.99989 9.35084V4.76006ZM6.88878 5.18688V8.92409L9.55822 7.05548L6.88878 5.18688ZM5 13.5556C4.75454 13.5556 4.55556 13.7546 4.55556 14C4.55556 14.2454 4.75454 14.4444 5 14.4444H11C11.2454 14.4444 11.4444 14.2454 11.4444 14C11.4444 13.7546 11.2454 13.5556 11 13.5556H5Z" fill="#525252"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_3613_984">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="icon-quiz" viewBox="0 0 1024 1024" stroke="#1F272E">
|
||||
<path d="M512 0C229.232 0 0 229.232 0 512c0 282.784 229.232 512 512 512 282.784 0 512.017-229.216 512.017-512C1024.017 229.232 794.785 0 512 0zm0 961.008c-247.024 0-448-201.984-448-449.01 0-247.024 200.976-448 448-448s448.017 200.977 448.017 448S759.025 961.009 512 961.009zm-47.056-160.529h80.512v-81.248h-80.512zm46.112-576.944c-46.88 0-85.503 12.64-115.839 37.889-30.336 25.263-45.088 75.855-44.336 117.775l1.184 2.336h73.44c0-25.008 8.336-60.944 25.008-73.84 16.656-12.88 36.848-19.328 60.56-19.328 27.328 0 48.336 7.424 63.073 22.271 14.72 14.848 22.063 36.08 22.063 63.664 0 23.184-5.44 42.976-16.368 59.376-10.96 16.4-29.328 39.841-55.088 70.322-26.576 23.967-42.992 43.231-49.232 57.807-6.256 14.592-9.504 40.768-9.744 78.512h76.96c0-23.68 1.503-41.136 4.496-52.336 2.975-11.184 11.504-23.823 25.568-37.888 30.224-29.152 54.496-57.664 72.88-85.551 18.336-27.857 27.52-58.593 27.52-92.193 0-46.88-14.176-83.408-42.577-109.568-28.416-26.176-68.272-39.248-119.568-39.248z" fill="#1F272E"/>
|
||||
</svg>
|
||||
|
||||
<svg width="16" height="16" id="icon-quiz" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_3613_978)">
|
||||
<mask id="mask0_3613_978" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="16" height="16">
|
||||
<path d="M16 0H0V16H16V0Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_3613_978)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.1111 8C14.1111 11.3751 11.3751 14.1111 8 14.1111C4.62492 14.1111 1.88889 11.3751 1.88889 8C1.88889 4.62492 4.62492 1.88889 8 1.88889C11.3751 1.88889 14.1111 4.62492 14.1111 8ZM15 8C15 11.866 11.866 15 8 15C4.13401 15 1 11.866 1 8C1 4.13401 4.13401 1 8 1C11.866 1 15 4.13401 15 8ZM7.37988 9.72462V9.79298H8.39062V9.72462C8.39062 9.4512 8.41992 9.23307 8.47852 9.07031C8.53711 8.90427 8.62826 8.76269 8.75196 8.64551C8.87891 8.52832 9.04329 8.4095 9.24516 8.28907C9.56089 8.09375 9.80987 7.85775 9.99218 7.58106C10.1745 7.30111 10.2656 6.96093 10.2656 6.56055C10.2656 6.17644 10.1745 5.83952 9.99218 5.5498C9.81316 5.26009 9.56089 5.03386 9.23538 4.87109C8.90987 4.70834 8.52734 4.62695 8.08789 4.62695C7.69401 4.62695 7.33268 4.70345 7.0039 4.85644C6.67513 5.00619 6.41146 5.22916 6.21289 5.52539C6.01432 5.81836 5.90852 6.17644 5.89551 6.59961H6.96972C6.986 6.34896 7.04948 6.14551 7.16016 5.98926C7.27084 5.82975 7.40756 5.71257 7.57031 5.6377C7.73633 5.56283 7.90885 5.52539 8.08789 5.52539C8.28972 5.52539 8.47364 5.56771 8.63964 5.65235C8.80892 5.73698 8.94071 5.8558 9.0352 6.00879C9.1328 6.16179 9.1816 6.34244 9.1816 6.55078C9.1816 6.81771 9.11004 7.0472 8.96676 7.23926C8.82356 7.42806 8.64453 7.58594 8.42969 7.71289C8.21159 7.84961 8.02278 7.98958 7.86328 8.13281C7.70703 8.27278 7.58659 8.46322 7.50196 8.7041C7.42057 8.94169 7.37988 9.28187 7.37988 9.72462ZM7.37988 11.8682C7.51986 12.0016 7.69076 12.0684 7.89258 12.0684C8.09765 12.0684 8.26855 12.0016 8.40527 11.8682C8.54524 11.7315 8.61524 11.5638 8.61524 11.3652C8.61524 11.1634 8.54524 10.9957 8.40527 10.8623C8.26855 10.7256 8.09765 10.6572 7.89258 10.6572C7.69076 10.6572 7.51986 10.7256 7.37988 10.8623C7.24316 10.9957 7.17481 11.1634 7.17481 11.3652C7.17481 11.5638 7.24316 11.7315 7.37988 11.8682Z" fill="#525252"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_3613_978">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
||||
<svg id="icon-quiz-blue" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1511_749)">
|
||||
<path d="M512 0C229.232 0 0 229.232 0 512C0 794.784 229.232 1024 512 1024C794.784 1024 1024.02 794.784 1024.02 512C1024.02 229.232 794.785 0 512 0ZM512 961.008C264.976 961.008 64 759.024 64 511.998C64 264.974 264.976 63.998 512 63.998C759.024 63.998 960.017 264.975 960.017 511.998C960.017 759.021 759.025 961.009 512 961.009V961.008ZM464.944 800.479H545.456V719.231H464.944V800.479ZM511.056 223.535C464.176 223.535 425.553 236.175 395.217 261.424C364.881 286.687 350.129 337.279 350.881 379.199L352.065 381.535H425.505C425.505 356.527 433.841 320.591 450.513 307.695C467.169 294.815 487.361 288.367 511.073 288.367C538.401 288.367 559.409 295.791 574.146 310.638C588.866 325.486 596.209 346.718 596.209 374.302C596.209 397.486 590.769 417.278 579.841 433.678C568.881 450.078 550.513 473.519 524.753 504C498.177 527.967 481.761 547.231 475.521 561.807C469.265 576.399 466.017 602.575 465.777 640.319H542.737C542.737 616.639 544.24 599.183 547.233 587.983C550.208 576.799 558.737 564.16 572.801 550.095C603.025 520.943 627.297 492.431 645.681 464.544C664.017 436.687 673.201 405.951 673.201 372.351C673.201 325.471 659.025 288.943 630.624 262.783C602.208 236.607 562.352 223.535 511.056 223.535V223.535Z" fill="#2D95F0" stroke="#2D95F0"/>
|
||||
@@ -100,15 +70,4 @@
|
||||
<svg id="icon-green-check-circled" width="24" height="24" viewBox="0 0 27 27" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12ZM16.8734 10.1402C17.264 9.74969 17.264 9.11652 16.8734 8.726C16.4829 8.33547 15.8498 8.33547 15.4592 8.726L14.6259 9.55933L12.9592 11.226L10.333 13.8522L9.37345 12.8927L8.54011 12.0593C8.14959 11.6688 7.51643 11.6688 7.1259 12.0593C6.73538 12.4499 6.73538 13.083 7.1259 13.4735L7.95923 14.3069L9.6259 15.9735C9.81344 16.1611 10.0678 16.2664 10.333 16.2664C10.5982 16.2664 10.8526 16.1611 11.0401 15.9735L14.3734 12.6402L16.0401 10.9735L16.8734 10.1402Z" fill="#68D391"/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="icon-clock" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#1F272E" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="feather feather-clock">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<polyline points="12 6 12 12 16 14"></polyline>
|
||||
</svg>
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" id="icon-success" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 18.75C14.8325 18.75 18.75 14.8325 18.75 10C18.75 5.16751 14.8325 1.25 10 1.25C5.16751 1.25 1.25 5.16751 1.25 10C1.25 14.8325 5.16751 18.75 10 18.75ZM13.966 7.48104C14.1856 7.21471 14.1477 6.8208 13.8813 6.60122C13.615 6.38164 13.2211 6.41954 13.0015 6.68587L8.68984 11.9155L7.01289 9.74823C6.80165 9.47524 6.40911 9.42517 6.13611 9.6364C5.86311 9.84764 5.81304 10.2402 6.02428 10.5132L8.18004 13.2993C8.29633 13.4495 8.47467 13.5388 8.66468 13.5417C8.85468 13.5447 9.0357 13.461 9.15658 13.3144L13.966 7.48104Z" fill="#171717"/>
|
||||
</svg>
|
||||
<svg width="16" height="16" id="icon-drag" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 3C4 3.82843 4.67157 4.5 5.5 4.5C6.32843 4.5 7 3.82843 7 3C7 2.17157 6.32843 1.5 5.5 1.5C4.67157 1.5 4 2.17157 4 3ZM5.5 9.5C4.67157 9.5 4 8.82843 4 8C4 7.17157 4.67157 6.5 5.5 6.5C6.32843 6.5 7 7.17157 7 8C7 8.82843 6.32843 9.5 5.5 9.5ZM5.5 14.5C4.67157 14.5 4 13.8284 4 13C4 12.1716 4.67157 11.5 5.5 11.5C6.32843 11.5 7 12.1716 7 13C7 13.8284 6.32843 14.5 5.5 14.5ZM9 3C9 3.82843 9.67157 4.5 10.5 4.5C11.3284 4.5 12 3.82843 12 3C12 2.17157 11.3284 1.5 10.5 1.5C9.67157 1.5 9 2.17157 9 3ZM10.5 9.5C9.67157 9.5 9 8.82843 9 8C9 7.17157 9.67157 6.5 10.5 6.5C11.3284 6.5 12 7.17157 12 8C12 8.82843 11.3284 9.5 10.5 9.5ZM10.5 14.5C9.67157 14.5 9 13.8284 9 13C9 12.1716 9.67157 11.5 10.5 11.5C11.3284 11.5 12 12.1716 12 13C12 13.8284 11.3284 14.5 10.5 14.5Z" fill="#171717"/>
|
||||
</svg>
|
||||
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 15 KiB |
@@ -1,7 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" id="icon-upload" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 14.5C11.5899 14.5 14.5 11.5899 14.5 8C14.5 4.41015 11.5899 1.5 8 1.5C4.41015 1.5 1.5 4.41015 1.5 8C1.5 11.5899
|
||||
4.41015 14.5 8 14.5Z" stroke="#505A62" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8 4.75V11.1351" stroke="#505A62" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5.29102 7.45833L7.99935 4.75L10.7077 7.45833" stroke="#505A62" stroke-miterlimit="10" stroke-linecap="round"
|
||||
stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 637 B |
@@ -1,10 +0,0 @@
|
||||
<svg id="icon-youtube" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_779_38008)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.125 13.625H14.875C15.4273 13.625 15.875 13.1773 15.875 12.625V3.125C15.875 2.57272 15.4273 2.125 14.875 2.125H3.125C2.57272 2.125 2.125 2.57272 2.125 3.125V12.625C2.125 13.1773 2.57272 13.625 3.125 13.625ZM14.875 14.625C15.9796 14.625 16.875 13.7296 16.875 12.625V3.125C16.875 2.02043 15.9796 1.125 14.875 1.125H3.125C2.02043 1.125 1.125 2.02043 1.125 3.125V12.625C1.125 13.7296 2.02043 14.625 3.125 14.625H14.875ZM6.74988 5.35507C6.74988 4.74831 7.43289 4.39269 7.92997 4.74065L11.619 7.32298C12.0456 7.62156 12.0456 8.25325 11.619 8.55183L7.92998 11.1342C7.43289 11.4821 6.74988 11.1265 6.74988 10.5197V5.35507ZM7.74988 5.83524V10.0396L10.753 7.93741L7.74988 5.83524ZM5.625 15.25C5.34886 15.25 5.125 15.4739 5.125 15.75C5.125 16.0261 5.34886 16.25 5.625 16.25H12.375C12.6511 16.25 12.875 16.0261 12.875 15.75C12.875 15.4739 12.6511 15.25 12.375 15.25H5.625Z" fill="#171717"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_779_38008">
|
||||
<rect width="18" height="18" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -1,6 +1,5 @@
|
||||
frappe.ready(() => {
|
||||
setup_file_size();
|
||||
pin_header();
|
||||
|
||||
$(".join-batch").click((e) => {
|
||||
join_course(e);
|
||||
@@ -10,6 +9,14 @@ frappe.ready(() => {
|
||||
notify_user(e);
|
||||
});
|
||||
|
||||
$(".btn-chapter").click((e) => {
|
||||
add_chapter(e);
|
||||
});
|
||||
|
||||
$(document).on("click", ".btn-save-chapter", (e) => {
|
||||
save_chapter(e);
|
||||
});
|
||||
|
||||
$(".nav-link").click((e) => {
|
||||
change_hash(e);
|
||||
});
|
||||
@@ -24,36 +31,8 @@ frappe.ready(() => {
|
||||
generate_graph("Lesson Completion", "#lesson-completion");
|
||||
generate_course_completion_graph();
|
||||
}
|
||||
|
||||
expand_the_active_chapter();
|
||||
|
||||
$(".chapter-title")
|
||||
.unbind()
|
||||
.click((e) => {
|
||||
rotate_chapter_icon(e);
|
||||
});
|
||||
|
||||
$(".no-preview").click((e) => {
|
||||
show_no_preview_dialog(e);
|
||||
});
|
||||
|
||||
$("#create-class").click((e) => {
|
||||
open_class_dialog(e);
|
||||
});
|
||||
});
|
||||
|
||||
const pin_header = () => {
|
||||
const el = document.querySelector(".sticky");
|
||||
if (el) {
|
||||
const observer = new IntersectionObserver(
|
||||
([e]) =>
|
||||
e.target.classList.toggle("is-pinned", e.intersectionRatio < 1),
|
||||
{ threshold: [1] }
|
||||
);
|
||||
observer.observe(el);
|
||||
}
|
||||
};
|
||||
|
||||
const setup_file_size = () => {
|
||||
frappe.provide("frappe.form.formatters");
|
||||
frappe.form.formatters.FileSize = file_size;
|
||||
@@ -70,7 +49,7 @@ const file_size = (value) => {
|
||||
|
||||
const join_course = (e) => {
|
||||
e.preventDefault();
|
||||
let course = $("#outline-heading").attr("data-course");
|
||||
let course = $(e.currentTarget).attr("data-course");
|
||||
if (frappe.session.user == "Guest") {
|
||||
window.location.href = `/login?redirect-to=/courses/${course}`;
|
||||
return;
|
||||
@@ -104,7 +83,7 @@ const join_course = (e) => {
|
||||
|
||||
const notify_user = (e) => {
|
||||
e.preventDefault();
|
||||
var course = decodeURIComponent($("#outline-heading").attr("data-course"));
|
||||
var course = decodeURIComponent($(e.currentTarget).attr("data-course"));
|
||||
if (frappe.session.user == "Guest") {
|
||||
window.location.href = `/login?redirect-to=/courses/${course}`;
|
||||
return;
|
||||
@@ -133,6 +112,65 @@ const notify_user = (e) => {
|
||||
});
|
||||
};
|
||||
|
||||
const add_chapter = (e) => {
|
||||
if ($(".new-chapter").length) {
|
||||
scroll_to_chapter_container();
|
||||
return;
|
||||
}
|
||||
|
||||
let next_index = $("[data-index]").last().data("index") + 1 || 1;
|
||||
let add_after = $(`.chapter-parent:last`).length
|
||||
? $(`.chapter-parent:last`)
|
||||
: $("#outline-heading");
|
||||
|
||||
$(`<div class="chapter-parent chapter-edit new-chapter">
|
||||
<div contenteditable="true" data-placeholder="${__(
|
||||
"Chapter Name"
|
||||
)}" class="chapter-title-main"></div>
|
||||
<div class="chapter-description small my-2" contenteditable="true"
|
||||
data-placeholder="${__("Short Description")}"></div>
|
||||
<button class="btn btn-sm btn-secondary d-block btn-save-chapter"
|
||||
data-index="${next_index}"> ${__("Save")} </button>
|
||||
</div>`).insertAfter(add_after);
|
||||
|
||||
scroll_to_chapter_container();
|
||||
};
|
||||
|
||||
const scroll_to_chapter_container = () => {
|
||||
$([document.documentElement, document.body]).animate(
|
||||
{
|
||||
scrollTop: $(".new-chapter").offset().top,
|
||||
},
|
||||
1000
|
||||
);
|
||||
$(".new-chapter").find(".chapter-title-main").focus();
|
||||
};
|
||||
|
||||
const save_chapter = (e) => {
|
||||
let target = $(e.currentTarget);
|
||||
let parent = target.closest(".chapter-parent");
|
||||
|
||||
frappe.call({
|
||||
method: "lms.lms.doctype.lms_course.lms_course.save_chapter",
|
||||
args: {
|
||||
course: $("#title").data("course"),
|
||||
title: parent.find(".chapter-title-main").text(),
|
||||
chapter_description: parent.find(".chapter-description").text(),
|
||||
idx: target.data("index"),
|
||||
chapter: target.data("chapter") ? target.data("chapter") : "",
|
||||
},
|
||||
callback: (data) => {
|
||||
frappe.show_alert({
|
||||
message: __("Saved"),
|
||||
indicator: "green",
|
||||
});
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const generate_graph = (chart_name, element, type = "line") => {
|
||||
let date = frappe.datetime;
|
||||
|
||||
@@ -188,164 +226,3 @@ const change_hash = (e) => {
|
||||
const open_tab = () => {
|
||||
$(`a[href="${window.location.hash}"]`).click();
|
||||
};
|
||||
|
||||
const expand_the_first_chapter = () => {
|
||||
let elements = $(".course-home-outline .collapse");
|
||||
elements.each((i, element) => {
|
||||
if (i < 1) {
|
||||
show_section(element);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const expand_the_active_chapter = () => {
|
||||
/* Find anchor matching the URL for course details page */
|
||||
let selector = $(
|
||||
`a[href="${decodeURIComponent(window.location.pathname)}"]`
|
||||
).parent();
|
||||
|
||||
if (!selector.length) {
|
||||
selector = $(
|
||||
`a[href^="${decodeURIComponent(window.location.pathname)}"]`
|
||||
).parent();
|
||||
}
|
||||
if (selector.length && $(".course-details-page").length) {
|
||||
expand_for_course_details(selector);
|
||||
} else if ($(".active-lesson").length) {
|
||||
/* For course home page */
|
||||
selector = $(".active-lesson");
|
||||
show_section(selector.parent().parent());
|
||||
} else {
|
||||
/* If no active chapter then exapand the first chapter */
|
||||
expand_the_first_chapter();
|
||||
}
|
||||
};
|
||||
|
||||
const expand_for_course_details = (selector) => {
|
||||
$(".lesson-info").removeClass("active-lesson");
|
||||
$(".lesson-info").each((i, elem) => {
|
||||
let href = $(elem).find("use").attr("href");
|
||||
href.endsWith("blue") &&
|
||||
$(elem)
|
||||
.find("use")
|
||||
.attr("href", href.substring(0, href.length - 5));
|
||||
});
|
||||
selector.addClass("active-lesson");
|
||||
|
||||
show_section(selector.parent().parent());
|
||||
};
|
||||
|
||||
const show_section = (element) => {
|
||||
$(element).addClass("show");
|
||||
$(element)
|
||||
.siblings(".chapter-title")
|
||||
.children(".chapter-icon")
|
||||
.css("transform", "rotate(90deg)");
|
||||
$(element).siblings(".chapter-title").attr("aria-expanded", true);
|
||||
};
|
||||
|
||||
const rotate_chapter_icon = (e) => {
|
||||
let icon = $(e.currentTarget).children(".chapter-icon");
|
||||
if (icon.css("transform") == "none") {
|
||||
icon.css("transform", "rotate(90deg)");
|
||||
} else {
|
||||
icon.css("transform", "none");
|
||||
}
|
||||
};
|
||||
|
||||
const show_no_preview_dialog = (e) => {
|
||||
$("#no-preview-modal").modal("show");
|
||||
};
|
||||
|
||||
const open_class_dialog = (e) => {
|
||||
this.class_dialog = new frappe.ui.Dialog({
|
||||
title: __("New Class"),
|
||||
fields: [
|
||||
{
|
||||
fieldtype: "Data",
|
||||
label: __("Title"),
|
||||
fieldname: "title",
|
||||
reqd: 1,
|
||||
default: class_info && class_info.title,
|
||||
},
|
||||
{
|
||||
fieldtype: "Date",
|
||||
label: __("Start Date"),
|
||||
fieldname: "start_date",
|
||||
reqd: 1,
|
||||
default: class_info && class_info.start_date,
|
||||
},
|
||||
{
|
||||
fieldtype: "Date",
|
||||
label: __("End Date"),
|
||||
fieldname: "end_date",
|
||||
reqd: 1,
|
||||
default: class_info && class_info.end_date,
|
||||
},
|
||||
{
|
||||
fieldtype: "Column Break",
|
||||
},
|
||||
{
|
||||
fieldtype: "Int",
|
||||
label: __("Seat Count"),
|
||||
fieldname: "seat_count",
|
||||
default: class_info && class_info.seat_count,
|
||||
},
|
||||
{
|
||||
fieldtype: "Time",
|
||||
label: __("Start Time"),
|
||||
fieldname: "start_time",
|
||||
default: class_info && class_info.start_time,
|
||||
},
|
||||
{
|
||||
fieldtype: "Time",
|
||||
label: __("End Time"),
|
||||
fieldname: "end_time",
|
||||
default: class_info && class_info.end_time,
|
||||
},
|
||||
{
|
||||
fieldtype: "Section Break",
|
||||
},
|
||||
{
|
||||
fieldtype: "Small Text",
|
||||
label: __("Description"),
|
||||
fieldname: "description",
|
||||
default: class_info && class_info.description,
|
||||
},
|
||||
],
|
||||
primary_action_label: __("Save"),
|
||||
primary_action: (values) => {
|
||||
create_class(values);
|
||||
},
|
||||
});
|
||||
this.class_dialog.show();
|
||||
};
|
||||
|
||||
const create_class = (values) => {
|
||||
frappe.call({
|
||||
method: "lms.lms.doctype.lms_class.lms_class.create_class",
|
||||
args: {
|
||||
title: values.title,
|
||||
start_date: values.start_date,
|
||||
end_date: values.end_date,
|
||||
description: values.description,
|
||||
seat_count: values.seat_count,
|
||||
start_time: values.start_time,
|
||||
end_time: values.end_time,
|
||||
name: class_info && class_info.name,
|
||||
},
|
||||
callback: (r) => {
|
||||
if (r.message) {
|
||||
frappe.show_alert({
|
||||
message: class_info
|
||||
? __("Class Updated")
|
||||
: __("Class Created"),
|
||||
indicator: "green",
|
||||
});
|
||||
this.class_dialog.hide();
|
||||
window.location.href = `/classes/${r.message.name}`;
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user