Compare commits

..

12 Commits

Author SHA1 Message Date
Jannat Patel
f38b1226df Merge pull request #321 from pateljannat/version-13-lms 2022-04-04 12:01:18 +05:30
Jannat Patel
1f3513db8b fix: get_palette function call for avatar 2022-04-04 09:47:27 +05:30
Jannat Patel
3eb0f13fb0 fix: moved Avatar widget to lms module 2022-04-04 09:32:25 +05:30
Jannat Patel
1277133ec6 refactor: removed unused modules from modules.txt 2022-04-04 09:24:03 +05:30
Jannat Patel
7337aea0dc refactor: renamed school references to lms 2022-04-04 08:23:07 +05:30
Jannat Patel
32b601cf34 refactor: renamed school to lms 2022-04-01 20:11:07 +05:30
Jannat Patel
d4dc901925 fix: rectified css path in hooks 2021-12-06 17:47:55 +05:30
Jannat Patel
64e581533b fix: included build files through hooks 2021-12-06 17:36:46 +05:30
Jannat Patel
0873d704d2 fix: reverting to old build system 2021-12-06 16:33:23 +05:30
Jannat Patel
8ee57f0254 fix: remove page_renderer and references 2021-12-06 13:10:42 +05:30
Jannat Patel
7c5021132d fix: remove page renderer for profile page 2021-12-06 10:51:50 +05:30
Jannat Patel
740c0d10ca get profile url jinja function issue 2021-12-03 16:29:29 +05:30
474 changed files with 13689 additions and 30855 deletions

View File

@@ -9,7 +9,7 @@ root = true
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_style = tab
indent_style = space
trim_trailing_whitespace = true
# Python
@@ -26,4 +26,4 @@ indent_style = tab
# HTML, CSS, javascript, JSON and YAML
[*.{html,css,js,json,yml,yaml}]
indent_size = 4
indent_size = 2

37
.flake8
View File

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

View File

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

View File

@@ -32,7 +32,7 @@ jobs:
- name: setup python
uses: actions/setup-python@v2
with:
python-version: '3.10'
python-version: '3.9'
- name: setup node
uses: actions/setup-node@v2
with:
@@ -65,7 +65,7 @@ jobs:
run: bench new-site --mariadb-root-password root --admin-password admin frappe.local
- name: install lms app
working-directory: /home/runner/frappe-bench
run: bench --site frappe.local install-app lms
run: bench --verbose --site frappe.local install-app lms
- name: setup requirements
working-directory: /home/runner/frappe-bench
run: bench setup requirements --dev

View File

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

View File

@@ -1,19 +0,0 @@
name: Semantic Pull Request
on:
push:
branches: [ main ]
pull_request: {}
jobs:
# This workflow contains a single job called "build"
semantic:
name: Validate PR title
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- uses: zeke/semantic-pull-requests@main

View File

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

View File

@@ -1,90 +1,25 @@
<p align="center">
<a href="https://www.frappelms.com/">
<img src="https://www.frappelms.com/files/flms.svg" alt="Frappe LMS" width="100" height="100">
</a>
<p align="center">Easy to use, open source, learning management system.</p>
</p>
## LMS
<p align="center">
<a href="https://github.com/frappe/lms/blob/main/LICENSE">
<img alt="license" src="https://img.shields.io/badge/license-AGPLv3-blue">
</a>
</p>
Create online courses without much hassle.
<img width="1402" alt="Lesson" src="https://frappelms.com/files/fs-banner71f330.png">
<details>
<summary>Show more screenshots</summary>
<img width="1520" alt="ss1" src="https://user-images.githubusercontent.com/31363128/210056046-584bc8aa-d28c-4514-b031-73817012837d.png">
<img width="830" alt="ss2" src="https://user-images.githubusercontent.com/31363128/210056097-36849182-6db0-43a2-8c62-5333cd2aedf4.png">
<img width="941" alt="ss3" src="https://user-images.githubusercontent.com/31363128/210056134-01a7c429-1ef4-434e-9d43-128dda35d7e5.png">
</details>
Frappe LMS is an easy-to-use, open-source learning management system. You can use it to create and share online courses. The app has a clear UI that helps students focus only on what's important and assists in distraction-free learning.
You can create courses and lessons through simple forms. Lessons can be in the form of text, videos, quizzes or a combination of all these. You can keep your students engaged with quizzes to help revise and test the concepts learned. Course Instructors and Students can reach out to each other through the discussions section available for each lesson and get queries resolved.
![Course Home](/lms/public/images/course-home.png)
## Features
- Create online courses. 📚
- Add detailed descriptions and preview videos to the course. 🎬
- Add videos, quizzes, and assignments to your lessons and make them interesting and interactive 📝
- Discussions section below each lesson where instructors and students can interact with each other. 💬
- Create classes to group your students based on courses and track their progress 🏛
- Statistics dashboard that provides all important numbers at a glimpse. 📈
- Job Board where users can post and look for jobs. 💼
- People directory with each person's profile page 👨‍👩‍👧‍👦
- Set cover image, profile photo, short bio, and other professional information. 🦹🏼‍♀️
- Simple layout that optimizes readability 🤓
- Delightful user experience in overall usage ✨
## Tech Stack
1. Simple Backend Forms.
1. The UI is clean and minimal.
1. Lessons can be in the form of texts, videos, quizzes or a combination of all of these.
Frappe LMS is built on [Frappe Framework](https://frappeframework.com) which is a batteries-included python web framework.
These are some of the tools it's built on:
- [Python](https://www.python.org)
- [Redis](https://redis.io/)
- [MariaDB](https://mariadb.org/)
- [Socket.io](https://socket.io/)
## Development Setup
## Local Setup
### Docker
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 lms/docker
docker-compose up
```
Wait for some time until the setup script creates a site. After that, you can access `http://localhost:8000` in your browser and the app's login screen should show up.
### Frappe Bench
Currently, this app depends on the `develop` branch of [frappe](https://github.com/frappe/frappe).
1. Setup frappe-bench by following [this guide](https://frappeframework.com/docs/v14/user/en/installation)
1. In the frappe-bench directory, run `bench start` and keep it running. Open a new terminal session and cd into the `frappe-bench` directory.
1. Run the following commands:
```sh
bench new-site lms.test
bench get-app lms
bench --site lms.test install-app lms
bench --site lms.test add-to-hosts
1. Now, you can access the site at `http://gameplan.test:8080`
1. [Through Docker](docker-installation.md)
1. [Direct install through bench](bench-installation.md)
## Deployment
Frappe LMS is an app built on top of the Frappe Framework. So, you can follow any deployment guide for hosting a Frappe Framework-based site.
### Contributing
### Managed Hosting
Frappe LMS can be deployed in a few clicks on [Frappe Cloud](https://frappecloud.com/marketplace/apps/lms).
### Self-hosting
If you want to self-host, you can follow official [Frappe Bench Installation](https://github.com/frappe/bench#installation) instructions.
## Bugs and Feature Requests
If you find any bugs or have a feature idea for the app, feel free to report them here on [GitHub Issues](https://github.com/frappe/lms/issues). Make sure you share enough information (app screenshots, browser console screenshots, stack traces, etc) for project maintainers.
1. [Contribution Guidelines](Contribution.md)
## License
Distributed under [GNU AFFERO GENERAL PUBLIC LICENSE](license.txt)
[GNU AFFERO GENERAL PUBLIC LICENSE](license.txt)

View File

@@ -3,7 +3,7 @@ To setup the repository locally follow the steps mentioned below:
1. Install bench and setup a frappe-bench directory by following the [Installation Steps](https://frappeframework.com/docs/user/en/installation).
1. Start the server by running bench start.
1. In a separate terminal window, create a new site by running bench new-site lms.test.
1. Fork the LMS app
1. Fork the lms app
1. Run bench get-app <url-of-your-form>.
1. Run bench --site lms.test install-app lms.
1. Map your site to localhost with the command ```bench --site lms.test add-to-hosts```

25
docker-compose.yml Normal file
View File

@@ -0,0 +1,25 @@
version: "3"
services:
mariadb:
image: mariadb
volumes:
- mariadb-storage:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=root
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
bench:
image: anandology/frappe-bench:2021.10
volumes:
- .:/opt/frappe-bench/apps/lms
environment:
- FRAPPE_APPS=lms
- FRAPPE_ALLOW_TESTS=true
- FRAPPE_SITE_NAME=frappe.localhost
depends_on:
- mariadb
ports:
- 8000:8000
- 9000:9000
volumes:
mariadb-storage: {}

View File

@@ -1,32 +0,0 @@
version: "3.7"
name: lms
services:
mariadb:
image: mariadb:10.6
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --skip-character-set-client-handshake
- --skip-innodb-read-only-compressed # Temporary fix for MariaDB 10.6
environment:
MYSQL_ROOT_PASSWORD: 123
volumes:
- mariadb-data:/var/lib/mysql
redis:
image: redis:alpine
frappe:
image: frappe/bench:latest
command: bash /workspace/init.sh
environment:
- SHELL=/bin/bash
working_dir: /home/frappe
volumes:
- .:/workspace
ports:
- 8000:8000
- 9000:9000
volumes:
mariadb-data:

View File

@@ -1,39 +0,0 @@
#!bin/bash
if [ -d "/home/frappe/frappe-bench/apps/frappe" ]; then
echo "Bench already exists, skipping init"
cd frappe-bench
bench start
else
echo "Creating new bench..."
fi
bench init --skip-redis-config-generation frappe-bench
cd frappe-bench
# Use containers instead of localhost
bench set-mariadb-host mariadb
bench set-redis-cache-host redis:6379
bench set-redis-queue-host redis:6379
bench set-redis-socketio-host redis:6379
# Remove redis, watch from Procfile
sed -i '/redis/d' ./Procfile
sed -i '/watch/d' ./Procfile
bench get-app lms
bench new-site lms.localhost \
--force \
--mariadb-root-password 123 \
--admin-password admin \
--no-mariadb-socket
bench --site lms.localhost install-app lms
bench --site lms.localhost set-config developer_mode 1
bench --site lms.localhost clear-cache
bench --site lms.localhost set-config mute_emails 1
bench use lms.localhost
bench start

View File

@@ -1 +1,7 @@
__version__ = "0.0.1"
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
__version__ = '0.0.1'
# load the methods from the lms api
from .lms import api # noqa

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,114 +0,0 @@
[
{
"docstatus": 0,
"doctype": "Function",
"function": "Consulting",
"modified": "2021-12-14 15:16:26.189604",
"name": "Consulting"
},
{
"docstatus": 0,
"doctype": "Function",
"function": "Data & Analytics",
"modified": "2021-12-14 15:16:26.341051",
"name": "Data & Analytics"
},
{
"docstatus": 0,
"doctype": "Function",
"function": "Design & Creative",
"modified": "2021-12-14 15:16:26.343820",
"name": "Design & Creative"
},
{
"docstatus": 0,
"doctype": "Function",
"function": "Engineering (Non Software)",
"modified": "2021-12-14 15:16:26.346329",
"name": "Engineering (Non Software)"
},
{
"docstatus": 0,
"doctype": "Function",
"function": "Finance, Investment & Accounting",
"modified": "2021-12-14 15:16:26.351518",
"name": "Finance, Investment & Accounting"
},
{
"docstatus": 0,
"doctype": "Function",
"function": "Human Resource & Recruiting",
"modified": "2021-12-14 15:16:26.354310",
"name": "Human Resource & Recruiting"
},
{
"docstatus": 0,
"doctype": "Function",
"function": "Legal",
"modified": "2021-12-14 15:16:26.356844",
"name": "Legal"
},
{
"docstatus": 0,
"doctype": "Function",
"function": "Marketing, Advertising & PR",
"modified": "2021-12-14 15:16:26.359511",
"name": "Marketing, Advertising & PR"
},
{
"docstatus": 0,
"doctype": "Function",
"function": "Operations & Admin",
"modified": "2021-12-14 15:16:26.362571",
"name": "Operations & Admin"
},
{
"docstatus": 0,
"doctype": "Function",
"function": "Project Management",
"modified": "2021-12-14 15:16:26.365033",
"name": "Project Management"
},
{
"docstatus": 0,
"doctype": "Function",
"function": "Product",
"modified": "2021-12-14 15:16:26.367804",
"name": "Product"
},
{
"docstatus": 0,
"doctype": "Function",
"function": "Research, Training & Education",
"modified": "2021-12-14 15:16:26.370085",
"name": "Research, Training & Education"
},
{
"docstatus": 0,
"doctype": "Function",
"function": "Sales & Customer Service",
"modified": "2021-12-14 15:16:26.372635",
"name": "Sales & Customer Service"
},
{
"docstatus": 0,
"doctype": "Function",
"function": "Supply Chain, Logistics Strategy & Management",
"modified": "2021-12-14 15:16:26.375091",
"name": "Supply Chain, Logistics Strategy & Management"
},
{
"docstatus": 0,
"doctype": "Function",
"function": "Other",
"modified": "2021-12-14 15:16:26.377761",
"name": "Other"
},
{
"docstatus": 0,
"doctype": "Function",
"function": "Engineering (Software & IT)",
"modified": "2021-12-14 15:16:26.348864",
"name": "Engineering (Software & IT)"
}
]

View File

@@ -1,170 +0,0 @@
[
{
"docstatus": 0,
"doctype": "Industry",
"industry": "Accounting",
"modified": "2021-12-14 15:21:18.044741",
"name": "Accounting"
},
{
"docstatus": 0,
"doctype": "Industry",
"industry": "Ads, Marketing, PR & Events",
"modified": "2021-12-14 15:21:18.069794",
"name": "Ads, Marketing, PR & Events"
},
{
"docstatus": 0,
"doctype": "Industry",
"industry": "Agriculture, Fishing & Forestry",
"modified": "2021-12-14 15:21:18.074148",
"name": "Agriculture, Fishing & Forestry"
},
{
"docstatus": 0,
"doctype": "Industry",
"industry": "Architecture",
"modified": "2021-12-14 15:21:18.078250",
"name": "Architecture"
},
{
"docstatus": 0,
"doctype": "Industry",
"industry": "Aviation & Aerospace",
"modified": "2021-12-14 15:21:18.082127",
"name": "Aviation & Aerospace"
},
{
"docstatus": 0,
"doctype": "Industry",
"industry": "Banking, Financial Services & Insurance",
"modified": "2021-12-14 15:21:18.087499",
"name": "Banking, Financial Services & Insurance"
},
{
"docstatus": 0,
"doctype": "Industry",
"industry": "Biotech & Pharmaceuticals",
"modified": "2021-12-14 15:21:18.092094",
"name": "Biotech & Pharmaceuticals"
},
{
"docstatus": 0,
"doctype": "Industry",
"industry": "Construction & Real Estate",
"modified": "2021-12-14 15:21:18.095728",
"name": "Construction & Real Estate"
},
{
"docstatus": 0,
"doctype": "Industry",
"industry": "Consulting & Professional Services",
"modified": "2021-12-14 15:21:18.099171",
"name": "Consulting & Professional Services"
},
{
"docstatus": 0,
"doctype": "Industry",
"industry": "Energy & Utilities",
"modified": "2021-12-14 15:21:18.114022",
"name": "Energy & Utilities"
},
{
"docstatus": 0,
"doctype": "Industry",
"industry": "Food & Beverages",
"modified": "2021-12-14 15:21:18.124655",
"name": "Food & Beverages"
},
{
"docstatus": 0,
"doctype": "Industry",
"industry": "Health & Medical",
"modified": "2021-12-14 15:21:18.127914",
"name": "Health & Medical"
},
{
"docstatus": 0,
"doctype": "Industry",
"industry": "Hospitality & Tourism",
"modified": "2021-12-14 15:21:18.130783",
"name": "Hospitality & Tourism"
},
{
"docstatus": 0,
"doctype": "Industry",
"industry": "IT / Ecommerce / Internet",
"modified": "2021-12-14 15:21:18.134069",
"name": "IT / Ecommerce / Internet"
},
{
"docstatus": 0,
"doctype": "Industry",
"industry": "Manufacturing & Production",
"modified": "2021-12-14 15:21:18.137362",
"name": "Manufacturing & Production"
},
{
"docstatus": 0,
"doctype": "Industry",
"industry": "Media & Entertainment",
"modified": "2021-12-14 15:21:18.140180",
"name": "Media & Entertainment"
},
{
"docstatus": 0,
"doctype": "Industry",
"industry": "Public Service & NGOs",
"modified": "2021-12-14 15:21:18.143125",
"name": "Public Service & NGOs"
},
{
"docstatus": 0,
"doctype": "Industry",
"industry": "Retail, Fashion & FMCG",
"modified": "2021-12-14 15:21:18.146020",
"name": "Retail, Fashion & FMCG"
},
{
"docstatus": 0,
"doctype": "Industry",
"industry": "Staffing & Recruiting",
"modified": "2021-12-14 15:21:18.149084",
"name": "Staffing & Recruiting"
},
{
"docstatus": 0,
"doctype": "Industry",
"industry": "Transportation & Logistics",
"modified": "2021-12-14 15:21:18.151952",
"name": "Transportation & Logistics"
},
{
"docstatus": 0,
"doctype": "Industry",
"industry": "Security & Law Enforcement",
"modified": "2021-12-14 15:21:18.154929",
"name": "Security & Law Enforcement"
},
{
"docstatus": 0,
"doctype": "Industry",
"industry": "Other",
"modified": "2021-12-14 15:21:18.157766",
"name": "Other"
},
{
"docstatus": 0,
"doctype": "Industry",
"industry": "Education & Training",
"modified": "2021-12-14 15:21:18.105864",
"name": "Education & Training"
},
{
"docstatus": 0,
"doctype": "Industry",
"industry": "Engineering",
"modified": "2021-12-14 15:21:18.119149",
"name": "Engineering"
}
]

View File

@@ -1,9 +1,11 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from . import __version__ as app_version
app_name = "frappe_lms"
app_title = "Frappe LMS"
app_name = "lms"
app_title = "LMS"
app_publisher = "Frappe"
app_description = "Frappe LMS App"
app_description = "LMS"
app_icon = "octicon octicon-file-directory"
app_color = "grey"
app_email = "school@frappe.io"
@@ -17,9 +19,9 @@ app_license = "AGPL"
# app_include_js = "/assets/lms/js/lms.js"
# include js, css files in header of web template
web_include_css = "lms.bundle.css"
web_include_css = "/assets/css/lms.css"
# web_include_css = "/assets/lms/css/lms.css"
web_include_js = ["website.bundle.js"]
#web_include_js = "website.bundle.js"
# include custom scss in every website theme (without file extension ".scss")
# website_theme_scss = "lms/public/scss/website"
@@ -45,7 +47,7 @@ web_include_js = ["website.bundle.js"]
# website user home page (by Role)
# role_home_page = {
# "Role": "home_page"
# "Role": "home_page"
# }
# Generators
@@ -58,12 +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"
after_uninstall = "lms.install.after_uninstall"
setup_wizard_requires = "assets/lms/js/setup_wizard.js"
# after_install = "lms.install.after_install"
# Desk Notifications
# ------------------
@@ -89,7 +86,7 @@ setup_wizard_requires = "assets/lms/js/setup_wizard.js"
override_doctype_class = {
"User": "lms.overrides.user.CustomUser",
"Web Template": "lms.overrides.web_template.CustomWebTemplate",
"Web Template": "lms.overrides.web_template.CustomWebTemplate"
}
# Document Events
@@ -97,19 +94,18 @@ override_doctype_class = {
# Hook on document methods and events
doc_events = {
"Discussion Reply": {"after_insert": "lms.lms.utils.create_notification_log"},
"Course Lesson": {"on_update": "lms.lms.doctype.lms_quiz.lms_quiz.update_lesson_info"},
}
# Scheduled Tasks
# ---------------
# scheduler_events = {
# "daily": [
# "erpnext.stock.reorder_item.reorder_item"
# ]
# }
#scheduler_events = {
# "daily": [
# "erpnext.stock.reorder_item.reorder_item"
# ]
#}
fixtures = ["Custom Field", "Function", "Industry"]
fixtures = ["Custom Field"]
# Testing
# -------
@@ -139,98 +135,23 @@ website_route_rules = [
{"from_route": "/sketches/<sketch>", "to_route": "sketches/sketch"},
{"from_route": "/courses/<course>", "to_route": "courses/course"},
{"from_route": "/courses/<course>/<certificate>", "to_route": "courses/certificate"},
{"from_route": "/hackathons/<hackathon>", "to_route": "hackathons/hackathon"},
{"from_route": "/hackathons/<hackathon>/<project>", "to_route": "hackathons/project"},
{"from_route": "/courses/<course>/learn", "to_route": "batch/learn"},
{
"from_route": "/courses/<course>/learn/<int:chapter>.<int:lesson>",
"to_route": "batch/learn",
},
{"from_route": "/quizzes", "to_route": "batch/quiz_list"},
{"from_route": "/quizzes/<quizname>", "to_route": "batch/quiz"},
{"from_route": "/classes/<classname>", "to_route": "classes/class"},
{"from_route": "/courses/<course>/learn/<int:chapter>.<int:lesson>", "to_route": "batch/learn"},
{"from_route": "/courses/<course>/progress", "to_route": "batch/progress"},
{"from_route": "/courses/<course>/join", "to_route": "batch/join"},
{"from_route": "/courses/<course>/manage", "to_route": "cohorts"},
{"from_route": "/courses/<course>/cohorts/<cohort>", "to_route": "cohorts/cohort"},
{
"from_route": "/courses/<course>/cohorts/<cohort>/<page>",
"to_route": "cohorts/cohort",
},
{
"from_route": "/courses/<course>/subgroups/<cohort>/<subgroup>",
"to_route": "cohorts/subgroup",
},
{
"from_route": "/courses/<course>/subgroups/<cohort>/<subgroup>/<page>",
"to_route": "cohorts/subgroup",
},
{
"from_route": "/courses/<course>/join/<cohort>/<subgroup>/<invite_code>",
"to_route": "cohorts/join",
},
{"from_route": "/users", "to_route": "profiles/profile"},
{"from_route": "/jobs/<job>", "to_route": "jobs/job"},
{
"from_route": "/classes/<classname>/students/<username>",
"to_route": "/classes/progress",
},
{"from_route": "/assignments/<assignment>", "to_route": "assignments/assignment"},
{"from_route": "/users/<string(minlength=4):username>", "to_route": "profiles/profile"}
]
website_redirects = [
{"source": "/update-profile", "target": "/edit-profile"},
{"source": "/dashboard", "target": "/courses"},
]
update_website_context = [
"lms.widgets.update_website_context",
'lms.widgets.update_website_context',
]
jinja = {
"methods": [
"lms.page_renderers.get_profile_url",
"lms.overrides.user.get_enrolled_courses",
"lms.overrides.user.get_course_membership",
"lms.overrides.user.get_authored_courses",
"lms.overrides.user.get_palette",
"lms.lms.utils.get_membership",
"lms.lms.utils.get_lessons",
"lms.lms.utils.get_tags",
"lms.lms.utils.get_instructors",
"lms.lms.utils.get_students",
"lms.lms.utils.get_average_rating",
"lms.lms.utils.is_certified",
"lms.lms.utils.get_lesson_index",
"lms.lms.utils.get_lesson_url",
"lms.lms.utils.get_chapters",
"lms.lms.utils.get_slugified_chapter_title",
"lms.lms.utils.get_progress",
"lms.lms.utils.render_html",
"lms.lms.utils.is_mentor",
"lms.lms.utils.is_cohort_staff",
"lms.lms.utils.get_mentors",
"lms.lms.utils.get_reviews",
"lms.lms.utils.is_eligible_to_review",
"lms.lms.utils.get_initial_members",
"lms.lms.utils.get_sorted_reviews",
"lms.lms.utils.is_instructor",
"lms.lms.utils.convert_number_to_character",
"lms.lms.utils.get_signup_optin_checks",
"lms.lms.utils.get_popular_courses",
"lms.lms.utils.format_amount",
"lms.lms.utils.first_lesson_exists",
"lms.lms.utils.get_courses_under_review",
"lms.lms.utils.has_course_instructor_role",
"lms.lms.utils.has_course_moderator_role",
"lms.lms.utils.get_certificates",
"lms.lms.utils.format_number",
"lms.lms.utils.get_lesson_count",
"lms.lms.utils.get_all_memberships",
"lms.lms.utils.get_filtered_membership",
"lms.lms.utils.show_start_learing_cta",
"lms.lms.utils.can_create_courses",
],
"filters": [],
}
## Specify the additional tabs to be included in the user profile page.
## Each entry must be a subclass of lms.lms.plugins.ProfileTab
# profile_tabs = []
@@ -238,55 +159,19 @@ jinja = {
## Specify the extension to be used to control what scripts and stylesheets
## to be included in lesson pages. The specified value must be be a
## subclass of lms.plugins.PageExtension
# lms_lesson_page_extension = None
# lms = None
# lms_lesson_page_extensions = [
# "lms.plugins.LiveCodeExtension"
# ]
has_website_permission = {
"LMS Certificate Evaluation": "lms.lms.doctype.lms_certificate_evaluation.lms_certificate_evaluation.has_website_permission"
}
profile_mandatory_fields = [
"first_name",
"last_name",
"user_image",
"bio",
"linkedin",
"education",
"skill",
"preferred_functions",
"preferred_industries",
"dream_companies",
"attire",
"collaboration",
"role",
"location_preference",
"time",
"company_type",
]
#lms_lesson_page_extensions = [
# "lms.plugins.LiveCodeExtension"
#]
## Markdown Macros for Lessons
lms_markdown_macro_renderers = {
"Exercise": "lms.plugins.exercise_renderer",
"Quiz": "lms.plugins.quiz_renderer",
"YouTubeVideo": "lms.plugins.youtube_video_renderer",
"Video": "lms.plugins.video_renderer",
"Assignment": "lms.plugins.assignment_renderer",
"Exercise": "lms.plugins.exercise_renderer",
"Quiz": "lms.plugins.quiz_renderer",
"YouTubeVideo": "lms.plugins.youtube_video_renderer",
"Video": "lms.plugins.video_renderer"
}
# page_renderer to manage profile pages
page_renderer = [
"lms.page_renderers.ProfileRedirectPage",
"lms.page_renderers.ProfilePage",
]
# set this to "/" to have profiles on the top-level
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"

View File

@@ -1,129 +0,0 @@
import frappe
from frappe.desk.page.setup_wizard.setup_wizard import add_all_roles_to
def after_install():
add_pages_to_nav()
def after_sync():
create_lms_roles()
set_default_home()
add_all_roles_to("Administrator")
def add_pages_to_nav():
pages = [
{"label": "Explore", "idx": 1},
{"label": "Courses", "url": "/courses", "parent": "Explore", "idx": 2},
{"label": "Classes", "url": "/classes", "parent": "Explore", "idx": 3},
{"label": "Statistics", "url": "/statistics", "parent": "Explore", "idx": 4},
{"label": "Jobs", "url": "/jobs", "parent": "Explore", "idx": 5},
{"label": "People", "url": "/community", "parent": "Explore", "idx": 6},
]
for page in pages:
filters = frappe._dict()
if page.get("url"):
filters["url"] = ["like", "%" + page.get("url") + "%"]
else:
filters["label"] = page.get("label")
if not frappe.db.exists("Top Bar Item", filters):
frappe.get_doc(
{
"doctype": "Top Bar Item",
"label": page.get("label"),
"url": page.get("url"),
"parent_label": page.get("parent"),
"idx": page.get("idx"),
"parent": "Website Settings",
"parenttype": "Website Settings",
"parentfield": "top_bar_items",
}
).save()
def after_uninstall():
delete_custom_fields()
def create_lms_roles():
create_course_creator_role()
create_moderator_role()
def set_default_home():
frappe.db.set_value("Portal Settings", None, "default_portal_home", "/courses")
def create_course_creator_role():
if not frappe.db.exists("Role", "Course Creator"):
role = frappe.get_doc(
{
"doctype": "Role",
"role_name": "Course Creator",
"home_page": "",
"desk_access": 0,
}
)
role.save(ignore_permissions=True)
def create_moderator_role():
if not frappe.db.exists("Role", "Moderator"):
role = frappe.get_doc(
{
"doctype": "Role",
"role_name": "Moderator",
"home_page": "",
"desk_access": 0,
}
)
role.save(ignore_permissions=True)
def delete_custom_fields():
fields = [
"user_category",
"headline",
"college",
"city",
"verify_terms",
"country",
"preferred_location",
"preferred_functions",
"preferred_industries",
"work_environment_column",
"time",
"role",
"carrer_preference_details",
"skill",
"certification_details",
"internship",
"branch",
"github",
"medium",
"linkedin",
"profession",
"looking_for_job",
"cover_image" "work_environment",
"dream_companies",
"career_preference_column",
"attire",
"collaboration",
"location_preference",
"company_type",
"skill_details",
"certification",
"education",
"work_experience",
"education_details",
"hide_private",
"work_experience_details",
"profile_complete",
]
for field in fields:
frappe.db.delete("Custom Field", {"fieldname": field})
frappe.db.commit()

View File

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

View File

@@ -1,157 +0,0 @@
{
"actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "format: JOB-{#####}",
"creation": "2022-02-07 12:01:41.074418",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"job_title",
"location",
"disabled",
"column_break_5",
"type",
"status",
"section_break_6",
"description",
"company_details_section",
"company_name",
"company_website",
"column_break_11",
"company_logo",
"application_link"
],
"fields": [
{
"fieldname": "job_title",
"fieldtype": "Data",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Job Title",
"reqd": 1
},
{
"fieldname": "location",
"fieldtype": "Data",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Location",
"reqd": 1
},
{
"fieldname": "column_break_5",
"fieldtype": "Column Break"
},
{
"default": "Full Time",
"fieldname": "type",
"fieldtype": "Select",
"label": "Type",
"options": "Full Time\nPart Time\nFreelance\nContract",
"reqd": 1
},
{
"default": "Open",
"fieldname": "status",
"fieldtype": "Select",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Status",
"options": "Open\nClosed"
},
{
"fieldname": "section_break_6",
"fieldtype": "Section Break"
},
{
"fieldname": "description",
"fieldtype": "Text Editor",
"label": "Description",
"reqd": 1
},
{
"fieldname": "company_details_section",
"fieldtype": "Section Break",
"label": "Company Details"
},
{
"fieldname": "company_name",
"fieldtype": "Data",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Company Name",
"reqd": 1
},
{
"fieldname": "company_website",
"fieldtype": "Data",
"label": "Company Website",
"reqd": 1
},
{
"fieldname": "column_break_11",
"fieldtype": "Column Break"
},
{
"fieldname": "company_logo",
"fieldtype": "Attach Image",
"label": "Company Logo",
"reqd": 1
},
{
"fieldname": "application_link",
"fieldtype": "Data",
"label": "Application Form Link",
"reqd": 1
},
{
"default": "0",
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled"
}
],
"index_web_pages_for_search": 1,
"links": [],
"make_attachments_public": 1,
"modified": "2022-09-15 17:22:21.662675",
"modified_by": "Administrator",
"module": "Job",
"name": "Job Opportunity",
"naming_rule": "Expression",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"select": 1,
"share": 1,
"write": 1
},
{
"create": 1,
"email": 1,
"export": 1,
"if_owner": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "All",
"select": 1,
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "job_title"
}

View File

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

View File

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

View File

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

View File

@@ -1,54 +0,0 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2022-02-07 12:01:41.422955",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"allow_posting",
"title",
"subtitle"
],
"fields": [
{
"default": "0",
"fieldname": "allow_posting",
"fieldtype": "Check",
"label": "Allow Job Posting From Website"
},
{
"fieldname": "title",
"fieldtype": "Data",
"label": "Job Board Title"
},
{
"fieldname": "subtitle",
"fieldtype": "Data",
"label": "Job Board Subtitle"
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2022-02-11 15:56:38.958317",
"modified_by": "Administrator",
"module": "Job",
"name": "Job Settings",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

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

View File

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

View File

@@ -1,28 +0,0 @@
{
"attach_print": 0,
"channel": "Email",
"creation": "2021-12-30 12:29:56.533903",
"days_in_advance": 0,
"docstatus": 0,
"doctype": "Notification",
"document_type": "Job Opportunity",
"enabled": 1,
"event": "New",
"idx": 0,
"is_standard": 1,
"message": "Hey,\n\nA new Job Opportunity has been created. \n\n<p>Company Name: {{ doc.company_name}}</p>\n<p>Job Title: {{ doc.job_title}}</p>\n<p>Job Location: {{ doc.location}}</p><br>\n<p>Job Description: {{ doc.description}}</p><br>\n\n<p>Find all the posted jobs <a href=\"{{ frappe.utils.get_url() }}/app/job-opportunity\">here</a>.</p><br>\n",
"modified": "2021-12-30 12:54:13.382512",
"modified_by": "Administrator",
"module": "Job",
"name": "New job alert",
"owner": "Administrator",
"recipients": [
{
"receiver_by_role": "System Manager"
}
],
"send_system_notification": 0,
"send_to_all_assignees": 0,
"sender_email": "",
"subject": "New job added by {{ doc.company_name}}"
}

View File

@@ -1,10 +0,0 @@
Hey,
A new Job Opportunity has been created.
<p>Company Name: {{ doc.company_name}}</p>
<p>Job Title: {{ doc.job_title}}</p>
<p>Job Location: {{ doc.location}}</p><br>
<p>Job Description: {{ doc.description}}</p><br>
<p>Find all the posted jobs <a href="{{ frappe.utils.get_url() }}/app/job-opportunity">here</a>.</p><br>

View File

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

View File

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

View File

@@ -1,151 +0,0 @@
{
"accept_payment": 0,
"allow_comments": 0,
"allow_delete": 0,
"allow_edit": 1,
"allow_incomplete": 0,
"allow_multiple": 1,
"allow_print": 0,
"amount": 0.0,
"amount_based_on_field": 0,
"apply_document_permissions": 1,
"button_label": "Save",
"creation": "2021-12-27 17:02:12.461145",
"custom_css": "[data-doctype=\"Web Form\"] {\n max-width: 720px;\n margin: 6rem auto;\n}",
"doc_type": "Job Opportunity",
"docstatus": 0,
"doctype": "Web Form",
"idx": 0,
"is_standard": 1,
"list_columns": [],
"login_required": 1,
"max_attachment_size": 0,
"modified": "2022-09-15 17:22:43.957184",
"modified_by": "Administrator",
"module": "Job",
"name": "job-opportunity",
"owner": "Administrator",
"payment_button_label": "Buy Now",
"published": 1,
"route": "job-opportunity",
"show_attachments": 0,
"show_list": 1,
"show_sidebar": 0,
"success_message": "",
"success_url": "/jobs",
"title": "Job Opportunity",
"web_form_fields": [
{
"allow_read_on_all_link_options": 0,
"fieldname": "job_title",
"fieldtype": "Data",
"hidden": 0,
"label": "Job Title",
"max_length": 0,
"max_value": 0,
"read_only": 0,
"reqd": 1,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"fieldname": "location",
"fieldtype": "Data",
"hidden": 0,
"label": "Location",
"max_length": 0,
"max_value": 0,
"read_only": 0,
"reqd": 1,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"default": "Full Time",
"fieldname": "type",
"fieldtype": "Select",
"hidden": 0,
"label": "Type",
"max_length": 0,
"max_value": 0,
"options": "Full Time\nPart Time\nFreelance\nContract",
"read_only": 0,
"reqd": 1,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"default": "Open",
"fieldname": "status",
"fieldtype": "Select",
"hidden": 0,
"label": "Status",
"max_length": 0,
"max_value": 0,
"options": "Open\nClosed",
"read_only": 0,
"reqd": 0,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"fieldname": "company_name",
"fieldtype": "Data",
"hidden": 0,
"label": "Company Name",
"max_length": 0,
"max_value": 0,
"read_only": 0,
"reqd": 1,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"fieldname": "company_website",
"fieldtype": "Data",
"hidden": 0,
"label": "Company Website",
"max_length": 0,
"max_value": 0,
"read_only": 0,
"reqd": 1,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"fieldname": "company_logo",
"fieldtype": "Attach Image",
"hidden": 0,
"label": "Company Logo",
"max_length": 0,
"max_value": 0,
"read_only": 0,
"reqd": 1,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"fieldname": "application_link",
"fieldtype": "Data",
"hidden": 0,
"label": "Application Form Link",
"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": "Text Editor",
"hidden": 0,
"label": "Description",
"max_length": 0,
"max_value": 0,
"read_only": 0,
"reqd": 1,
"show_in_filter": 0
}
]
}

View File

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

View File

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

View File

@@ -2,26 +2,23 @@
"based_on": "creation",
"chart_name": "Course Enrollments",
"chart_type": "Count",
"color": "#4463F0",
"creation": "2021-09-30 12:23:09.414853",
"custom_options": "{\"type\": \"line\", \"axisOptions\": {\"xIsSeries\": 1}, \"lineOptions\": {\"regionFill\": 1}}",
"docstatus": 0,
"doctype": "Dashboard Chart",
"document_type": "LMS Batch Membership",
"dynamic_filters_json": "[]",
"filters_json": "[]",
"group_by_type": "Count",
"idx": 1,
"idx": 0,
"is_public": 1,
"is_standard": 1,
"last_synced_on": "2022-10-20 10:46:56.859520",
"modified": "2022-10-20 11:30:26.863008",
"last_synced_on": "2021-10-17 13:32:21.745498",
"modified": "2021-10-21 17:31:06.997133",
"modified_by": "Administrator",
"module": "LMS",
"name": "Course Enrollments",
"number_of_groups": 0,
"owner": "basawaraj@erpnext.com",
"roles": [],
"source": "",
"time_interval": "Daily",
"timeseries": 1,

View File

@@ -1,33 +0,0 @@
{
"based_on": "creation",
"chart_name": "Lesson Completion",
"chart_type": "Count",
"color": "#4463F0",
"creation": "2022-11-09 16:52:19.021695",
"custom_options": "{\"type\": \"line\", \"axisOptions\": {\"xIsSeries\": 1}, \"lineOptions\": {\"regionFill\": 1}}",
"docstatus": 0,
"doctype": "Dashboard Chart",
"document_type": "LMS Course Progress",
"dynamic_filters_json": "[]",
"filters_json": "[]",
"group_by_type": "Count",
"idx": 0,
"is_public": 1,
"is_standard": 1,
"modified": "2022-11-09 16:52:52.241756",
"modified_by": "Administrator",
"module": "LMS",
"name": "Lesson Completion",
"number_of_groups": 0,
"owner": "Administrator",
"parent_document_type": "",
"roles": [],
"source": "",
"time_interval": "Daily",
"timeseries": 1,
"timespan": "Last Month",
"type": "Line",
"use_report_chart": 0,
"value_based_on": "",
"y_axis": []
}

View File

@@ -1,33 +0,0 @@
{
"based_on": "creation",
"chart_name": "New Signups",
"chart_type": "Count",
"color": "#4463F0",
"creation": "2021-09-28 18:57:50.047656",
"custom_options": "{\"type\": \"line\", \"axisOptions\": {\"xIsSeries\": 1}, \"lineOptions\": {\"regionFill\": 1}}",
"docstatus": 0,
"doctype": "Dashboard Chart",
"document_type": "User",
"dynamic_filters_json": "[]",
"filters_json": "[]",
"group_by_type": "Count",
"idx": 1,
"is_public": 1,
"is_standard": 1,
"last_synced_on": "2022-10-20 10:46:56.849265",
"modified": "2022-10-20 11:31:17.184897",
"modified_by": "Administrator",
"module": "LMS",
"name": "New Signups",
"number_of_groups": 0,
"owner": "basawaraj@erpnext.com",
"roles": [],
"source": "",
"time_interval": "Daily",
"timeseries": 1,
"timespan": "Last Quarter",
"type": "Line",
"use_report_chart": 0,
"value_based_on": "",
"y_axis": []
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,5 @@
{
"actions": [],
"autoname": "hash",
"creation": "2021-07-27 16:25:02.903245",
"doctype": "DocType",
"editable_grid": 1,
@@ -21,15 +20,13 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-03-15 09:39:41.937565",
"modified": "2021-09-30 10:35:30.014950",
"modified_by": "Administrator",
"module": "LMS",
"name": "Chapter Reference",
"naming_rule": "Random",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

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

View File

@@ -1,43 +0,0 @@
{
"actions": [],
"autoname": "autoincrement",
"creation": "2022-11-09 16:23:26.454527",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"course",
"title"
],
"fields": [
{
"fieldname": "course",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Course",
"options": "LMS Course",
"reqd": 1
},
{
"fetch_from": "course.title",
"fieldname": "title",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Title",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-11-11 15:51:45.560864",
"modified_by": "Administrator",
"module": "LMS",
"name": "Class Course",
"naming_rule": "Autoincrement",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

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

View File

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

View File

@@ -1,51 +0,0 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2022-11-09 16:20:44.602545",
"default_view": "List",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"student",
"student_name",
"username"
],
"fields": [
{
"fieldname": "student",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Student",
"options": "User",
"reqd": 1
},
{
"fetch_from": "student.full_name",
"fieldname": "student_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Student Name",
"read_only": 1
},
{
"fetch_from": "student.username",
"fieldname": "username",
"fieldtype": "Data",
"label": "Username",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-11-15 11:13:39.410578",
"modified_by": "Administrator",
"module": "LMS",
"name": "Class Student",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -59,7 +59,7 @@
"link_fieldname": "chapter"
}
],
"modified": "2022-03-14 17:57:00.707416",
"modified": "2021-09-29 15:33:44.611229",
"modified_by": "Administrator",
"module": "LMS",
"name": "Course Chapter",
@@ -77,26 +77,11 @@
"role": "System Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"email": 1,
"export": 1,
"if_owner": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "All",
"select": 1,
"share": 1,
"write": 1
}
],
"search_fields": "title",
"show_title_field_in_link": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "title",
"track_changes": 1
}

View File

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

View File

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

View File

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

View File

@@ -1,53 +0,0 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "field:evaluator",
"creation": "2022-03-29 10:51:47.667284",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"evaluator",
"schedule"
],
"fields": [
{
"fieldname": "evaluator",
"fieldtype": "Link",
"label": "Evaluator",
"options": "User",
"unique": 1
},
{
"fieldname": "schedule",
"fieldtype": "Table",
"label": "Schedule",
"options": "Evaluator Schedule"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2022-04-01 15:14:03.300260",
"modified_by": "Administrator",
"module": "LMS",
"name": "Course Evaluator",
"naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,148 +1,57 @@
// Copyright (c) 2021, FOSS United and contributors
// For license information, please see license.txt
frappe.ui.form.on("Course Lesson", {
setup: function (frm) {
frm.trigger("setup_help");
},
setup_help(frm) {
let quiz_link = `<a href="/app/lms-quiz"> ${__("Quiz List")} </a>`;
let exercise_link = `<a href="/app/exercise"> ${__(
"Exercise List"
)} </a>`;
let file_link = `<a href="/app/file"> ${__("File DocType")} </a>`;
frappe.ui.form.on('Course Lesson', {
setup: function (frm) {
frm.trigger('setup_help');
},
setup_help(frm) {
frm.get_field('help').html(`
<p>You can add some more additional content to the lesson using a special syntax. The table below mentions all types of dynamic content that you can add to the lessons and the syntax for the same.</p>
<div class="row font-weight-bold mb-3">
<div class="col-sm-4">
Content Type
</div>
<div class="col-sm-4">
Syntax
</div>
</div>
frm.get_field("help").html(`
<p>${__(
"You can add some more additional content to the lesson using a special syntax. The table below mentions all types of dynamic content that you can add to the lessons and the syntax for the same."
)}</p>
<table class="table">
<tr style="background-color: var(--fg-hover-color); font-weight: bold">
<th style="width: 20%;">
${__("Content Type")}
</th>
<th style="width: 40%;">
${__("Syntax")}
</th>
<th>
${__("Description")}
</th>
</tr>
<tr>
<td>
${__("YouTube Video")}
</td>
<td>
{{ YouTubeVideo("unique_embed_id") }}
</td>
<td>
<span>
${__(
"Copy and paste the syntax in the editor. Replace 'embed_src' with the embed source that YouTube provides. To get the source, follow the steps mentioned below."
)}
</span>
<ul class="p-4">
<li>
${__("Upload the video on youtube.")}
</li>
<li>
${__(
"When you share a youtube video, it shows an option called Embed."
)}
</li>
<li>
${__(
"On clicking it, it provides an iframe. Copy the source (src) of the iframe and paste it here."
)}
</li>
</ul>
</td>
</tr>
<tr>
<td>
${__("Quiz")}
</td>
<td>
{{ Quiz("lms_quiz_id") }}
</td>
<td>
${__(
"Copy and paste the syntax in the editor. Replace 'lms_quiz_id' with the ID of the Quiz you want to add. You can get the ID of the quiz from the {0}.",
[quiz_link]
)}
</td>
</tr>
<tr>
<td>
${__("Video")}
</td>
<td>
{{ Video("url_of_source") }}
</td>
<td>
${__(
"Upload a video from your local machine to the {0}. Copy and paste this syntax in the editor. Replace 'url_of_source' with the File URL field of the document you created in the File DocType.",
[file_link]
)}
</td>
</tr>
<tr>
<td>
${"Exercise"}
</td>
<td>
{{ Exercise("exercise_id") }}
</td>
<td>
${__(
"Copy and paste the syntax in the editor. Replace 'exercise_id' with the ID of the Exercise you want to add. You can get the ID of the exercise from the {0}.",
[exercise_link]
)}
</td>
</tr>
<tr>
<td>
${__("Assignment")}
</td>
<td>
{{ Assignment("id-filetype") }}
</td>
</tr>
</table>
<hr>
<table class="table">
<tr style="background-color: var(--fg-hover-color); font-weight: bold">
<th style="width: 90%">
${__("Supported File Types for Assignment")}
</th>
<th>
${__("Syntax")}
</th>
</tr>
<tr>
<td>
.doc, .docx, .xml
<td>
${__("Document")}
</td>
</tr>
<tr>
<td>
.pdf
</td>
<td>
${__("PDF")}
</td>
</tr>
<tr>
<td>
.png, .jpg, .jpeg
</td>
<td>
${__("Image")}
</td>
</tr>
</table>
`);
},
<div class="row mb-3">
<div class="col-sm-4">
Video
</div>
<div class="col-sm-4">
{{ Video("url_of_source") }}
</div>
</div>
<div class="row mb-3">
<div class="col-sm-4">
YouTube Video
</div>
<div class="col-sm-4">
{{ YouTubeVideo("unique_embed_id") }}
</div>
</div>
<div class="row mb-3">
<div class="col-sm-4">
Exercise
</div>
<div class="col-sm-4">
{{ Exercise("exercise_name") }}
</div>
</div>
<div class="row mb-3">
<div class="col-sm-4">
Quiz
</div>
<div class="col-sm-4">
{{ Quiz("lms_quiz_name") }}
</div>
</div>
`);
}
});

View File

@@ -1,6 +1,6 @@
{
"actions": [],
"allow_events_in_timeline": 1,
"allow_import": 1,
"allow_rename": 1,
"autoname": "format:{####} {title}",
"creation": "2021-05-03 06:21:12.995984",
@@ -9,20 +9,11 @@
"engine": "InnoDB",
"field_order": [
"chapter",
"course",
"include_in_preview",
"column_break_4",
"title",
"include_in_preview",
"index_label",
"section_break_6",
"youtube",
"column_break_9",
"quiz_id",
"section_break_16",
"question",
"column_break_15",
"file_type",
"section_break_11",
"body",
"help_section",
"help"
@@ -32,7 +23,6 @@
"fieldname": "chapter",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Course Chapter",
"options": "Course Chapter",
"reqd": 1
@@ -79,63 +69,11 @@
{
"fieldname": "help",
"fieldtype": "HTML"
},
{
"fetch_from": "chapter.course",
"fieldname": "course",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Course",
"options": "LMS Course",
"read_only": 1
},
{
"description": "Quiz will appear at the bottom of the lesson.",
"fieldname": "quiz_id",
"fieldtype": "Data",
"label": "Quiz ID"
},
{
"fieldname": "column_break_9",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_11",
"fieldtype": "Section Break"
},
{
"description": "YouTube Video will appear at the top of the lesson.",
"fieldname": "youtube",
"fieldtype": "Data",
"label": "YouTube Video URL"
},
{
"fieldname": "section_break_16",
"fieldtype": "Section Break",
"label": "Assignment"
},
{
"description": "Assignment will appear at the bottom of the lesson.",
"fieldname": "question",
"fieldtype": "Small Text",
"label": "Question"
},
{
"fieldname": "file_type",
"fieldtype": "Select",
"label": "File Type",
"mandatory_depends_on": "question",
"options": "\nImage\nDocument\nPDF"
},
{
"fieldname": "column_break_15",
"fieldtype": "Column Break"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2022-12-28 16:01:42.191123",
"modified": "2021-10-11 15:07:38.134808",
"modified_by": "Administrator",
"module": "LMS",
"name": "Course Lesson",
@@ -151,26 +89,11 @@
"read": 1,
"report": 1,
"role": "System Manager",
"select": 1,
"share": 1,
"write": 1
},
{
"create": 1,
"email": 1,
"export": 1,
"if_owner": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "All",
"select": 1,
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

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

View File

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

View File

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

View File

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

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