frappe chinese document translation plan (#4689)

* [add] Chinese documents init.

* Videos chinese document translated

* Bench chinese documents translated half

* Bench chinese documents translated

* Fixed several mistakes for bench guides

* Frappé Tutorial chinese version in translation
This commit is contained in:
Loocor 2018-01-23 03:20:46 -06:00 committed by Rushabh Mehta
parent 08f9cf7f4b
commit c43d1fac59
133 changed files with 4879 additions and 2 deletions

View file

@ -5,4 +5,7 @@ Select your language
1. [English](/docs/user/en)
1. [Français](/docs/user/fr)
1. [Português](/docs/user/pt)
1. [Español](/docs/user/es)
1. [Español](/docs/user/es)
1. [简体中文](/docs/user/zh)

View file

@ -1,4 +1,5 @@
en
fr
pt
es
es
zh

View file

View file

View file

@ -0,0 +1,28 @@
# 为站点添加自定义域名
你可以为站点添加 **多个自定义域名**,只需运行:
bench setup add-domain [desired-domain]
在运行该命令时,将询问你要为哪个站点设置自定义域名。
你可以使用以下选项为自定义域名设置 SSL
--ssl-certificate [path-to-certificate]
--ssl-certificate-key [path-to-certificate-key]
例如:
bench setup add-domain custom.erpnext.com --ssl-certificate /etc/letsencrypt/live/erpnext.cert --ssl-certificate-key /etc/letsencrypt/live/erpnext.key
域名配置保存在各站点自己的 site_config.json 配置文件里:
"domains": [
{
"ssl_certificate": "/etc/letsencrypt/live/erpnext.cert",
"domain": "erpnext.com",
"ssl_certificate_key": "/etc/letsencrypt/live/erpnext.key"
}
],
**你需要通过运行 `bench setup nginx` 重新生成 Nginx 配置,并重新加载 Nginx 服务使你的自定义域名生效**

View file

@ -0,0 +1,46 @@
# 配置 HTTPS
### 获取必要的文件
你可以从受信任的证书颁发机构获得 SSL 证书或生成自己的证书。对于自签名证书,浏览器将显示一个 “该证书不受信任” 的警告。[这里是通过 Let's Encrypt 获取免费 SSL 证书的教程](lets-encrypt-ssl-setup.html)
这些必要的文件包括:
* 证书 (通常后缀名为 .crt)
* 解密的私钥
如果你有多个证书(初级和中级),你将需要将它们连接起来。例如,
cat your_certificate.crt CA.crt >> certificate_bundle.crt
还要确保你的私钥不可读。一般来说,它只有 root 才能是所有者和可读
chown root private.key
chmod 600 private.key
### 将两个文件移动到适当的位置
mkdir /etc/nginx/conf.d/ssl
mv private.key /etc/nginx/conf.d/ssl/private.key
mv certificate_bundle.crt /etc/nginx/conf.d/ssl/certificate_bundle.crt
### 设置 Nginx 配置
为你的站点设置证书和私钥的路径
bench set-ssl-certificate site1.local /etc/nginx/conf.d/ssl/certificate_bundle.crt
bench set-ssl-key site1.local /etc/nginx/conf.d/ssl/private.key
### 生成 Nginx 配置
bench setup nginx
### 重启 Nginx
sudo service nginx reload
systemctl reload nginx # for CentOS 7
现在你已完成了 SSL 的配置,所有 HTTP 通信都将重定向到 HTTPS

View file

@ -0,0 +1,33 @@
# 诊断计划任务
<!-- markdown -->
如果你在计划任务中遇到延迟,或者似乎无法运行,可以运行几个命令来诊断问题。
### `bench doctor`
这将按顺序给出如下输出:
- 各站点计划任务状态
- 执行单元 (Workers) 数量
- 待处理任务
预期输出:
Workers online: 0
-----None Jobs-----
### `bench --site [site-name] show-pending-jobs`
这将按顺序给出如下输出:
- 队列
- 队列任务
预期输出:
-----Pending Jobs-----
### `bench purge-jobs`
这将从所有队列中删除全部待处理的任务

View file

@ -0,0 +1,3 @@
# 指南
{index}

View file

@ -0,0 +1,11 @@
configuring-https
lets-encrypt-ssl-setup
diagnosing-the-scheduler
how-to-change-host-name-from-localhost
manual-setup
setup-multitenancy
setup-production
setup-ssl
stop-production-and-start-development
updating
setting-limits

View file

@ -0,0 +1,100 @@
# 通过 Let's Encrypt 配置 HTTPS
## 必备条件
1. 你需要有 DNS 多租户 (Multitenant) 设置
2. 你的网站应可通过有效的域名访问
3. 你需要服务器的 root 权限
**注意 : Let's Encrypt 证书将每三个月到期**
## 使用 Bench 命令
运行:
sudo -H bench setup lets-encrypt [site-name]
您将碰到几个提示,请做出相应地回应。该命令还会向用户的 crontab 添加一个任务,每月尝试更新证书。
### 自定义域名
你还可以为[自定义域名](adding-custom-domains.html)设置 Let's Encrypt。使用 `--custom-domain` 选项即可
sudo -H bench setup lets-encrypt [site-name] --custom-domain [custom-domain]
### 刷新证书
你可以使用以下命令手工刷新证书:
sudo bench renew-lets-encrypt
<hr>
## 手工方式
### 下载适当的 Certbot-auto 脚本到 /opt 目录中
https://certbot.eff.org/
### 停止 nginx 服务
$ sudo service nginx stop
### 运行 Certbot
$ ./opt/certbot-auto certonly --standalone
在 letsencrypt 初始化后,将提示你输入一些信息。取决于你之前是否使用了 Let's Encrypt这个提示可能会有所不同但我们会第一时间指导你完成。
在提示中,输入用于通知、以及恢复丢失密钥的电子邮件地址:
![](https://assets.digitalocean.com/articles/letsencrypt/le-email.png)
你必须同意 Let's Encrypt 的订阅协议,选择同意:
![](https://assets.digitalocean.com/articles/letsencrypt/le-agreement.png)
然后输入你的域名。注意,如果你希望把一个证书用到多个域名上 (例如 example.com、www.example.com) ,确保像如下那样全部包含它们:
![](https://assets.digitalocean.com/articles/letsencrypt/le-domain.png)
### 证书文件
获得证书后,你将拥有以下 PEM 编码的文件:
* **cert.pem**: 你的域名证书
* **chain.pem**: Let's Encrypt 链证书
* **fullchain.pem**: 合并的 cert.pem 和 chain.pem
* **privkey.pem**: 你的证书私钥
这些证书文件保存在 `/etc/letsencrypt/live/example.com` 文件夹
### 为你的站点配置证书
转到你的 erpnext 站点 site_config.json
$ cd frappe-bench/sites/{{site_name}}
添加以下两行到你的 site_config.json 文件中
"ssl_certificate": "/etc/letsencrypt/live/example.com/fullchain.pem",
"ssl_certificate_key": "/etc/letsencrypt/live/example.com/privkey.pem"
重新生成 Nginx 配置
$ bench setup nginx
重启 Nginx 服务
$ sudo service nginx restart
---
### 自动更新 (实验功能)
以 root 或拥有 superuser 权限的用户身份登录,运行 `crontab -e` 并输入:
# 每月第一个周一刷新 letsencrypt 证书,如果执行完成后将收到邮件提示
MAILTO="mail@example.com"
0 0 1-7 * * [ "$(date '+\%a')" = "Mon" ] && sudo service nginx stop && /opt/certbot-auto renew && sudo service nginx start

View file

@ -0,0 +1,75 @@
# 手工设置
手工设置
--------------
安装必备组件,
* [Python 2.7](https://www.python.org/download/releases/2.7/)
* [MariaDB](https://mariadb.org/)
* [Redis](http://redis.io/topics/quickstart)
* [WKHTMLtoPDF with patched QT](http://wkhtmltopdf.org/downloads.html) (生成 pdf 需要)
[在 OSX 下安装必备组件](https://github.com/frappe/bench/wiki/Installing-Bench-Pre-requisites-on-MacOSX)
以*非 root 用户*安装 bench
git clone https://github.com/frappe/bench bench-repo
sudo pip install -e bench-repo
提示:请不要删除上述命令将创建的 bench 目录
从现有安装迁移
------------------------------------
如果想从 ERPNext 版本 3 迁移,请参照[这里](https://github.com/frappe/bench/wiki/Migrating-from-ERPNext-version-3)的说明
如果想从老版本的 Bench 迁移,请参照[这里](https://github.com/frappe/bench/wiki/Migrating-from-old-bench)的说明
基本用法
===========
* 创建新的 Bench
命令 init 将创建一个安装了 frappe 框架的 bench 目录。它将被设置为定期备份和每天一次的自动更新。
bench init frappe-bench && cd frappe-bench
* 添加应用
命令 get-app 获取并安装 frappe 应用。例如:
- [erpnext](https://github.com/frappe/erpnext)
- [erpnext_shopify](https://github.com/frappe/erpnext_shopify)
- [paypal_integration](https://github.com/frappe/paypal_integration)
bench get-app erpnext https://github.com/frappe/erpnext
* 添加站点
Frappé 应用由 frappe 站点运行,您需要至少创建一个站点。命令 new-site 可以达到该目的:
bench new-site site1.local
* 启动 Bench
要启动 Bench使用 `bench start` 命令
bench start
要登录 Frappé / ERPNext打开你的浏览器输入 `localhost:8000`
默认用户名为 "Administrator",密码则是当你设置新站点时指定的密码。
配置 ERPNext
==================
要安装 ERPNext只需运行
```
bench install-app erpnext
```
现在你可以使用 `bench start` 启动或[设置生产用 Bench](setup-production.html)

View file

@ -0,0 +1,38 @@
# 为站点配置限额
Frappé v7 加入了对站点进行限额设置的支持。这些限额在站点文件夹内的 `site_config.json` 文件中设置,
{
"db_name": "xxxxxxxxxx",
"db_password": "xxxxxxxxxxxx",
"limits": {
"emails": 1500,
"space": 0.157,
"expiry": "2016-07-25",
"users": 1
}
}
你可以运行以下命令设置限制:
bench --site [sitename] set-limit [limit] [value]
你也可以同时设置多个限制,运行以下命令:
bench --site [sitename] set-limits --limit [limit] [value] --limit [limit-2] [value-2]
你可以设置的有效限制有:
- **users** - 限制站点的最大用户数
- **emails** - 限制站每月邮件的发送数量上限
- **space** - 限制站点可以使用的最大存储空间(GB)
- **email_group** - 限制邮件群组中允许的最大成员数量
- **expiry** - 站点的到期日期(带括号的 YYYY-MM-DD 格式)
例如:
bench --site site1.local set-limit users 5
你可以通过从工具栏/ AwesomeBar 打开 “使用信息” 页面查看使用情况。设置的限制会显示在该页面上。
<img class="screenshot" alt="Doctype Saved" src="/docs/assets/img/usage_info.png">

View file

@ -0,0 +1,54 @@
# 多租户模式配置
假设你已经运行了你的第一个站点,并完成了[生产环境部署](setup-production.html),这篇文章将展示如何托管你的第二个站点(或更多)。你的第一个站点自动设置为默认站点。你可以通过如下命令更改默认站点,
bench use sitename
基于端口的多租户模式
-----------------------
你可以创建新的站点并运行在不同的端口上(第一个站点运行在 80 端口)。
* 关闭基于 DNS 的多租户模式 (一次即可)
`bench config dns_multitenant off`
* 新增站点
`bench new-site site2name`
* 设置端口
`bench set-nginx-port site2name 82`
* 重新生成 Nginx 配置
`bench setup nginx`
* 重新加载 Nginx 服务
`sudo service nginx reload`
基于 DNS 的多租户模式
----------------------
将你的站点命名为主机名hostname即可。你的所有站点都将运行在相同的端口上并根据其主机名hostname自动选择。
基于DNS的多租户在做一个新的网站请执行以下步骤。
* 开启基于 DNS 的多租户模式 (一次即可)
`bench config dns_multitenant on`
* 新增站点
`bench new-site site2name`
* 重新生成 Nginx 配置
`bench setup nginx`
* 重新加载 Nginx 服务
`sudo service nginx reload`

View file

@ -0,0 +1,44 @@
# 生产环境部署
你可以通过配置 Supervisor、Nginx 来部署生产环境。如果你想把生产环境恢复为开发环境,请参考[这些命令](https://github.com/frappe/bench/wiki/Stopping-Production-and-starting-Development)。
#### 自动部署生产环境
运行命令 `sudo bench setup production` 将自动完成生产环境部署。
#### 手工部署生产环境
Supervisor
----------
Supervisor 确保 Frappé 系统进程保持运行并在它发生崩溃后自动重新启动。你可以使用命令 `bench setup supervisor` 生成 Supervisor 所需的配置。该配置可参考`config/supervisor.conf` 文件。你可以将该文件复制或链接到 supervisor 配置目录并重新加载它以使其生效。
例如,
```
bench setup supervisor
sudo ln -s `pwd`/config/supervisor.conf /etc/supervisor/conf.d/frappe-bench.conf
```
注意:对于 CentOS 7, 其扩展名应是 `ini`, 因此命令变成了:
```
bench setup supervisor
sudo ln -s `pwd`/config/supervisor.conf /etc/supervisor/conf.d/frappe-bench.ini #for CentOS 7 only
```
更新 supervisor 配置后需要重启 supervisor 管理的相关进程。要自动完成它,你需要使用命令 `sudo bench setup sudoers $(whoami)` 对 sudoers 进行配置。
Nginx
-----
Nginx 是一个 Web 服务器,我们用它来提供静态文件以及其他对 Frappe 请求的代理。你可以使用命令 `bench setup nginx` 生成 Supervisor 所需的配置。该配置可参考`config/nginx.conf` 文件。你可以将该文件复制或链接到 nginx 配置目录并重新加载它以使其生效。
例如,
```
bench setup nginx
sudo ln -s `pwd`/config/nginx.conf /etc/nginx/conf.d/frappe-bench.conf
```
注意:如果有另一个端口配置为 80 的服务存在,在你更改配置后重新启动 Nginx 可能失败(多数情况下导致 Nginx 的欢迎页出现)。你需要禁用此配置。通常它们位于 `/etc/nginx/conf.d/default.conf``/etc/nginx/conf.d/default` 中。

View file

@ -0,0 +1,3 @@
# Bench
{index}

View file

@ -0,0 +1 @@
guides

View file

@ -0,0 +1,24 @@
# 后台服务
外部服务
-----------------
* MariaDB (Frappe 数据库)
* Redis (Frappe 后台执行单元(Workers)和缓存查询)
* nginx (用于生产环境部署)
* supervisor (用于生产环境部署)
Frappé 进程
----------------
* WSGI 服务
* 该 WSGI 服务负责相应对 frappe 的 HTTP 请求。在开发场景下 (`bench serve``bench start`) 为 Werkzeug WSGI 服务,生产场景下则使用 gunicorn (由 supervisor 自动配置)。
* Redis 执行单元(Workers)进程
* 该 Celery 执行单元进程在 Frappé 系统里运行后台任务。当 supervisor 配置为生产环境时,这些进程在运行 `bench start` 时会自动启动。
* 计划任务进程
* 计划任务进程在 Frappé 系统里运行规划的任务。当 supervisor 配置为生产环境时,该进程在运行 `bench start` 时会自动启动。

View file

@ -0,0 +1,94 @@
# Bench 命令列表
### 常用
* `bench --version` - 显示 bench 版本
* `bench src` - 显示 bench 仓库目录
* `bench --help` - 显示所有命令和帮助
* `bench [command] --help` - 显示指定命令的帮助
* `bench init [bench-name]` - 创建新的工作台(bench) (在 home 目录下运行)
* `bench --site [site-name] COMMAND` - 指定命令应用的站点
* `bench update` - 从 bench 仓库和其他所有应用获取更新,应用补丁,重建 JS、CSS然后执行迁移操作
* `--pull` 获取所有应用的更新
* `--patch` 执行所有站点的迁移
* `--build` 重建 JS、CSS
* `--bench` 更新 bench
* `--requirements` 更新依赖
* `--restart-supervisor` 更新后重启 supervisor 进程
* `--upgrade` 进行主版本升级 ( 如 ERPNext 6 -> 7)
* `--no-backup` 更新前不进行备份
* `bench restart` 重启所有 bench 服务
* `bench backup` 备份
* `bench backup-all-sites` 备份所有站点
* `--with-files` 备份站点及其文件
* `bench restore` 恢复
* `--with-private-files` 恢复站点及私有文件 (tar 文件路径)
* `--with-public-files` 恢复站点及公共文件 (tar 文件路径)
* `bench migrate` 读取 JSON 文件并对数据库进行相应的更改
### 配置
* `bench config` - 更改 bench 配置
* `auto_update [on/off]` 启用/禁用 bench 自动更新
* `dns_multitenant [on/off]` 启用/禁用 DNS 多租户模式
* `http_timeout` 设置 http 超时时间
* `restart_supervisor_on_update` 启用/禁用 更新时自动重启 supervisor
* `serve_default_site` 配置 Nginx 默认站点
* `update_bench_on_update` 启用/禁用 bench 同步更新
* `bench setup` - 设置组件
* `auto-update` 为 bench 自动更新增加 cronjob 任务
* `backups ` 为 bench 备份增加 cronjob 任务
* `config ` 重写或生成 config.json
* `env ` 生成 bench virtualenv 环境
* `nginx ` 生成 nginx 配置文件
* `procfile ` 设置 bench 启动过程文件(Procfile)
* `production ` 设置 bench 为生产环境
* `redis ` 生成 redis 缓存配置文件
* `socketio ` 设置 socketio 服务所需的 Node 依赖环境
* `sudoers ` 增加命令到 sudoers 列表...
* `supervisor ` 生成 supervisor 配置文件
* `add-domain ` 增加站点自定义域名
* `firewall ` 设置防火墙并屏蔽除 22、80、443 之外的所有端口
* `ssh-port ` 更改 SSH 默认连接端口
### 开发
* `bench new-app [app-name]` 创建一个新的应用
* `bench get-app [repo-link]` - 从 git 仓库下载并安装一个应用
* `bench install-app [app-name]` 安装已有的应用
* `bench remove-from-installed-apps [app-name]` 从应用列表中移除应用
* `bench uninstall-app [app-name]` 删除应用及与该应用相关的一切 (须确保 Bench 在运行)
* `bench remove-app [app-name]` 从 bench 中彻底删除应用
* `bench --site [sitename] --force reinstall ` 全新数据库重新安装 (小心:将清除老的数据库)
* `bench new-site [sitename]` - 创建一个新的站点
* `--db-name` 数据库名称
* `--mariadb-root-username` MariaDB 数据库 root 用户名
* `--mariadb-root-password` MariaDB 数据库 root 密码
* `--admin-password` 新站点的管理员密码
* `--verbose` 显示详细信息
* `--force` 强制恢复 (如果站点已经存在)
* `--source_sql` 使用 SQL 文件初始化数据库
* `--install-app` 站点安装后安装应用
* `bench use [site]` 设置默认站点
* `bench drop-site` 从磁盘及数据库中完全移除站点
* `--root-login`
* `--root-password`
* `bench set-config [key] [value]` 为站点配置文件增加键值对
* `bench console` 打开 bench venv 下的 IPython 终端
* `bench execute` 执行任何应用内的方法
* 例如 : `bench execute frappe.utils.scheduler.enqueue_scheduler_events`
* `bench mysql` 打开 SQL 终端
* `bench run-tests` 运行测试
* `--app` 应用名称
* `--doctype` 用于测试的 DocType
* `--test` 具体测试
* `--module` 运行具有测试的特定模块
* `--profile` 运行具有测试的 Python 过程文件
* `bench disable-production` 禁用生产环境
### 计划任务
* `bench enable-scheduler` - 启用运行计划任务
* `bench doctor` - 显示有关后台执行单元的诊断信息
* `bench show-pending-jobs`- 显示未完成任务
* `bench purge-jobs` - 销毁所有未完成任务

View file

@ -0,0 +1,31 @@
# Bench Procfile
在**开发模式**下 `bench start` 使用 [honcho](http://honcho.readthedocs.org) 管理多个流程。
### 过程
运行 Frappe 所需的相关过程是:
1. `bench start` - Web 服务
4. `redis_cache` 用于缓存 (通常)
5. `redis_queue` 用于管理后台执行单元队列
6. `redis_socketio` 作为来自后台执行单元的实时消息代理
7. `web` 用于 frappe Web 服务
7. `socketio` 用于实时消息
3. `schedule` 用于触发定期任务
3. `worker_*` 用于 redis 执行单元处理异步任务
或者,如果你在开发 Frappe你可以添加 `bench watch` 自动创建桌面 JavaScript 应用。
### 例子
redis_cache: redis-server config/redis_cache.conf
redis_socketio: redis-server config/redis_socketio.conf
redis_queue: redis-server config/redis_queue.conf
web: bench serve --port 8000
socketio: /usr/bin/node apps/frappe/socketio.js
watch: bench watch
schedule: bench schedule
worker_short: bench worker --queue short
worker_long: bench worker --queue long
worker_default: bench worker --queue default

View file

@ -0,0 +1,3 @@
# 资源
{index}

View file

@ -0,0 +1,3 @@
background-services
bench-commands-cheatsheet
bench-procfile

View file

View file

View file

@ -0,0 +1,30 @@
# Adding Custom Button To Form
To create a custom button on your form, you need to edit the javascript file associated to your doctype. For example, If you want to add a custom button to User form then you must edit `user.js`.
In this file, you need to write a new method `add_custom_button` which should add a button to your form.
#### Function Signature for `add_custom_button(...)`
frm.add_custom_button(__(buttonName), function(){
//perform desired action such as routing to new form or fetching etc.
}, __(groupName));
#### Example-1: Adding a button to User form
We should edit `frappe\core\doctype\user\user.js`
frappe.ui.form.on('User', {
refresh: function(frm) {
...
frm.add_custom_button(__('Get User Email Address'), function(){
frappe.msgprint(frm.doc.email);
}, __("Utilities"));
...
}
});
You should be seeing a button on user form as shown below,
<img class="screenshot" alt="Custom Button" src="/docs/assets/img/app-development/add_custom_button.png">
<!-- markdown -->

View file

@ -0,0 +1,36 @@
# Adding Module Icons On Desktop
To create a module icon for a Page, List or Module, you will have to edit the `config/desktop.py` file in your app.
In this file you will have to write the `get_data` method that will return a dict object with the module icon parameters
### Example 1: Module Icon
def get_data():
return {
"Accounts": {
"color": "#3498db",
"icon": "octicon octicon-repo",
"type": "module"
},
}
### Example 2: List Icon
def get_data():
return {
"To Do": {
"color": "#f1c40f",
"icon": "fa fa-check",
"icon": "octicon octicon-check",
"label": _("To Do"),
"link": "List/ToDo",
"doctype": "ToDo",
"type": "list"
},
}
Note: Module views are visible based on permissions.
<!-- markdown -->

View file

@ -0,0 +1,23 @@
# Custom Module Icon
If you want to create a custom icon for your module, you will have to create an SVG file for your module and set the path to this file in the `desktop/config.py` of your app.<br>
This icon is loaded via AJAX first time, then it will be rendered.
Example:
from frappe import _
def get_data():
return {
"Frappé Apps": {
"color": "orange",
"icon": "assets/frappe/images/frappe.svg",
"label": _("Frappé.io Portal"),
"type": "module"
}
}
> PS: A great place to buy SVG icons for a low cost is the awesome [Noun Project](http://thenounproject.com/)
<!-- markdown -->

View file

@ -0,0 +1,121 @@
# Dialogs Types
Frappé provides a group of standard dialogs that are very useful while coding.
## Alert Dialog
<img class="screenshot" src="/docs/assets/img/app-development/show-alert.png">
Alert Dialog is used for showing non-obstructive messages.
It has 2 parameters:
- **txt:** The message to be shown in the `Alert Dialog`
- **seconds:** The duration that the message will be displayed. The default is `3 seconds`.
### Example
show_alert('Hi, do you have a new message', 5);
---
## Prompt Dialog
<img class="screenshot" src="/docs/assets/img/app-development/prompt.png">
Prompt Dialog is used for collecting data from users.
It has 4 parameters:
- **fields:** a list with the fields objects
- **callback:** a function to process the data in the dialog
- **title:** the title of the dialog
- **primary_label:** the label of the primary button
### Example
frappe.prompt([
{'fieldname': 'birth', 'fieldtype': 'Date', 'label': 'Birth Date', 'reqd': 1}
],
function(values){
show_alert(values, 5);
},
'Age verification',
'Subscribe me'
)
---
## Confirm Dialog
<img class="screenshot" src="/docs/assets/img/app-development/confirm-dialog.png">
Confirm Dialog is used to get a confirmation from the user before executing an action.
It has 3 arguments:
- **mesage:** The message to display in the dialog
- **onyes:** The callback on positive confirmation
- **oncancel:** The callback on negative confirmation
### Example
frappe.confirm(
'Are you sure to leave this page?',
function(){
window.close();
},
function(){
show_alert('Thanks for continue here!')
}
)
---
## Message Print
<img class="screenshot" src="/docs/assets/img/app-development/msgprint.png">
Message Print is used for showing information to users.
It has 2 arguments:
- **message:** The message to display. It can be a HTML string
- **title:** The title of the dialog
### Example
msgprint("<b>Server Status</b>"
+ "<hr>"
+ "<ul>"
+ "<li><b>28%</b> Memory</li>"
+ "<li><b>12%</b> Processor</li>"
+ "<li><b>0.3%</b> Disk</li>"
+ "</ul>", 'Server Info')
---
### Custom Dialog
<img class="screenshot" src="/docs/assets/img/app-development/dialog.png">
You can extend and build your own custom dialogs using `frappe.ui.Dialog`
### Example
var d = new frappe.ui.Dialog({
'fields': [
{'fieldname': 'ht', 'fieldtype': 'HTML'},
{'fieldname': 'today', 'fieldtype': 'Date', 'default': frappe.datetime.nowdate()}
],
primary_action: function(){
d.hide();
show_alert(d.get_values());
}
});
d.fields_dict.ht.$wrapper.html('Hello World');
d.show();
<!-- markdown -->

View file

@ -0,0 +1,31 @@
# Executing Code On Doctype Events
To execute code when a DocType is inserted, validated (before saving), updated, submitted, cancelled, deleted, you must write in the DocType's controller module.
#### 1. Controller Module
The controller module exists in the `doctype` folder in the Module of the `DocType`. For example, the controller for **ToDo** exists in `frappe/desk/doctype/todo/todo.py` (version 5). A controller template is created when the DocType is created. which looks like
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
class CustomType(Document):
pass
#### 2. Document Properties
All the fields and child tables are available to the class as attributes. For example the **name** property is `self.name`
#### 3. Adding Methods
In this module, you can add standard methods to the class that are called when a document of that type is created. Standard Handlers are:
1. `autoname`: Called while naming. You can set the `self.name` property in the method.
1. `before_insert`: Called before a document is inserted.
1. `validate`: Called before document is saved. You can throw an exception if you don't want the document to be saved
1. `on_update`: Called after the document is inserted or updated in the database.
1. `on_submit`: Called after submission.
1. `on_cancel`: Called after cancellation.
1. `on_trash`: Called after document is deleted.

View file

@ -0,0 +1,15 @@
# Exporting Customizations to your App
A common use case is to extend a DocType via Custom Fields and Property Setters for a particular app. To save these settings to an app, go to **Customize Form**
You will see a button for **Export Customizations**
<img class="screenshot" src="/docs/assets/img/app-development/export-custom-1.png">
Here you can select the module and whether you want these particular customizations to be synced after every update.
The customizations will be exported to a new folder `custom` in the module folder of your app. The customizations will be saved by the name of the DocType
<img class="screenshot" src="/docs/assets/img/app-development/export-custom-2.png">
When you do `bench update` or `bench migrate` these customizations will be synced to the app.

View file

@ -0,0 +1,15 @@
# Fetch a Field Value from a Document into a Transaction
Let's say, there is a custom field "VAT Number" in Supplier, which should be fetched in Purchase Order.
#### Steps:
1. Create a Custom Field **VAT Number** for *Supplier* document with *Field Type* as **Data**.
<img class="screenshot" src="/docs/assets/img/add-vat-number-in-supplier.png">
1. Create another Custom Field **VAT Number** for *Purchase Order* document, but in this case with *Field Type* as **Read Only** or check **Read Only** checkbox. Set the **Options** as `supplier.vat_number`.
<img class="screenshot" src="/docs/assets/img/add-vat-number-in-purchase-order.png">
1. Go to the user menu and click "Reload".
1. Now, on selection of Supplier in a new Purchase Order, **VAT Number** will be fetched automatically from the selected Supplier.
<img class="screenshot" src="/docs/assets/img/vat-number-fetched.png">

View file

@ -0,0 +1,70 @@
# Generating Documentation Website for your App
Frappé version 6.7 onwards includes a full-blown documentation generator so that you can easily create a website for your app that has both user docs and developers docs (auto-generated).
Version 8.7 onwards, these will be generated in a target app.
## Writing Docs
### 1. Setting up docs
The first step is to setup the docs folder. For that you must create a new file in your app `config/docs.py` if it is not auto-generated. In your `docs.py` file, add the following module properties.
source_link = "https://github.com/[orgname]/[reponame]"
headline = "This is what my app does"
sub_heading = "Slightly more details with key features"
long_description = """(long description in markdown)"""
def get_context(context):
# optional settings
# context.brand_html = 'Brand info on the top left'
# context.favicon = 'path to favicon'
#
# context.top_bar_items = [
# {"label": "About", "url": context.docs_base_url + "/about"},
# ]
pass
### 2. Add User Documentation
To add user documentation, add folders and pages in your `/docs/user` folder in the same way you would build a website pages in the `www` folder.
Some quick tips:
1. Add your pages as `.md` or `.html` pages
2. Optionally add `.css` files with the same name that will be automatically served
3. Add index by adding `{index}`
### 3. Linking
While linking make sure you add `/docs` to all your links.
{% raw %}<a href="/docs/user/link/to/page.html">Link Description</a>{% endraw %}
### 4. Adding Images
You can add images in the `/docs/assets` folder. You can add links to the images as follows:
{% raw %}<img src="/docs/assets/img/my-img/gif" class="screenshot">{% endraw %}
---
## Building Docs
You must create a new app that will have the output of the docs, which is called the "target" app. For example, the docs for ERPNext are hosted at erpnext.org, which is based on the app "foundation". You can create a new app just to push docs of any other app.
To output docs to another app,
bench --site [site] build-docs [app] --target [target_app]
This will create a new folder `/docs` inside the `www` folder of the target app and generate automatic docs (from code), model references and copy user docs and assets.
To view the docs, just go the the `/docs` url on your target app. Example:
https://erpnext.org/docs

View file

@ -0,0 +1,19 @@
# How Enable Developer Mode In Frappé
When you are in application design mode and you want the changes in your DocTypes, Reports etc to affect the app repository, you must be in **Developer Mode**.
To enable developer mode, update the `site_config.json` file of your site in the sites folder for example:
frappe-bench/sites/site1/site_config.json
Add this to the JSON object
"developer_mode": 1
After setting developer mode, clear the cache:
$ bench clear-cache
To view the full developer options, you must be logged in as the "Administrator" user.
<!-- markdown -->

View file

@ -0,0 +1,22 @@
# How To Create Custom Fields During App Installation
Your custom app can automatically add **Custom Fields** to DocTypes outside of your app when it is installed to a new site.
To do this, add the new custom fields that your app requires, using the Frappé web application.
In your `hooks.py` file, add `"Custom Fields"`
fixtures = ["Custom Field"]
Export fixtures before you commit your app with:
$ bench --site mysite export-fixtures
This will create a new folder called `fixtures` in your app folder and a `.csv` or `.json` file will be created with the definition of the custom fields you added.
This file will be automatically imported when the app is installed in a new site or updated via `bench update`.
Note: You can also add single DocTypes like "Website Settings" as fixtures
<!-- markdown -->

View file

@ -0,0 +1,104 @@
# How To Improve A Standard Control
Frappé has a couple of elegant and useful widgets, but some times we need to edit them to add small improvements. This small article will describe how to add new resources to the standard widgets.
Let me explain first our goal:
&gt; Add `many` alternative translations in `numerous records` and in a `lot of doctypes`
Look the highlighted sections in the __goal__, we have _many translations_ to add in _many records_ and in _many doctypes_, so, we heave a **many of work**, so we have a lot to do right?
The answer for this question is: _-Of course not! Because we know that if one element exists in many records and in many doctypes, this element is the `Control` or `Widget`_
So, what we need do, is improve your goal based on the `Control`, to reduce our quantity of work.
But, where will we find this magic element, the control? _-For now, we can look it in the JavaScript sources - let's look now at [Github](https://github.com/frappe/frappe/blob/develop/frappe/public/js/frappe/form/control.js#L13)_
&gt; Don't worry if you don't understand the code for now, our goal there is simplify our work.
Let's go ahead with the thought!
We know where we need to make the changes, but how will we dismember which are the controls that are affected by our feature and which aren't ?
We need to keep in mind, that `Control` are instance of `DocFields` and the `DocFields` have a field that is very important for us in this case, the field that will help us to dismember which are affected by our feature and which aren't is the field `options` in the `DocField`.
_-Wait!, we understood that the field `options` can help us, but, how will it help us?_
Good question, we will define a word to put in the `options` of the `DocFields` that we need to include the feature, this world will be **`Translatable`.**
&gt; If you forget how to customize the options of a field look [this article](https://kb.erpnext.com/kb/customize/creating-custom-link-field), it can refresh your knowledge.
Well, with the defined word in `options` of our selected `DocFields`, now is time to code:
_-At last, we think we would never stop talking!_
frappe.ui.form.ControlData = frappe.ui.form.ControlData.$extend({
make_input: function(){
var options = this.df.options;
if (!options || options!=="Translatable"){
this._super();
return;
}
var me = this;
$('<div class="link-field" style="position: relative;">\
<input type="text" class="input-with-feedback form-control">\
<span class="dialog-btn">\
<a class="btn-open no-decoration" title="' + __(" open="" translation")="" +="" '"="">\
<i class="fa fa-globe"></i></a>\
</span>\
</div>').prependTo(this.input_area);
this.$input_area = $(this.input_area);
this.$input = this.$input_area.find('input');
this.$btn = this.$input_area.find('.dialog-btn');
this.set_input_attributes();
this.$input.on("focus", function(){
me.$btn.toggle(true);
});
this.$input.on("blur", function(){
setTimeout(function(){ me.$btn.toggle(false) }, 500);
});
this.input = $this.input.get(0);
this.has_input = true;
var me = this;
this.setup_button();
},
setup_button: function(){
var me = this;
if (this.only_input){
this.$btn.remove();
return;
}
this.$btn.on("click", function(){
var value = me.get_value();
var options = me.df.options;
if (value &amp;&amp; options &amp;&amp; options==="Translatable"){
this.open_dialog();
}
});
},
open_dialog: function(){
var doc = this.doc;
if (!doc.__unsaved){
new frappe.ui.form.TranslationSelector({
doc: doc,
df: this.doc,
text: this.value
});
}
}
});
_-Other letter soup, for my gosh!_
In fact, it IS a soup of letters, for a newbie, but we are not a beginner.
Let me explain what this code does;
- At line 1 the code overrides the `ControlData` by one extended `Class` of itself.
- The method `make_input` checks if the docfield is **`Translatable`** to make the new `Control` if not, it calls the *original* `make_input` using `_super()`
- The method `setup_button` checks if the docfield is **`Translatable`** to enable it show a `dialog`
- The method `open_dialog` invokes a new instance of the `TranslationSelector` that we will create in the code below.
<!-- markdown -->

View file

@ -0,0 +1,3 @@
# App Development
{index}

View file

@ -0,0 +1,15 @@
adding-module-icons-on-desktop
custom-module-icon
generating-docs
how-enable-developer-mode-in-frappe
fetch-custom-field-value-from-master-to-all-related-transactions
executing-code-on-doctype-events
exporting-customizations
how-to-create-custom-fields-during-app-installation
insert-a-document-via-api
how-to-improve-a-standard-control
overriding-link-query-by-custom-script
single-type-doctype
trigger-event-on-deletion-of-grid-row
dialogs-types
using-html-templates-in-javascript

View file

@ -0,0 +1,55 @@
# Insert A Document Via Api
You can insert documents via a script using the `frappe.get_doc` method
### Examples:
#### 1. Insert a ToDo
todo = frappe.get_doc({"doctype":"ToDo", "description": "test"})
todo.insert()
---
#### 2. Insert without the user's permissions being checked:
todo = frappe.get_doc({"doctype":"ToDo", "description": "test"})
todo.insert(ignore_permissions = True)
---
#### 3. Submit after inserting
todo = frappe.get_doc({"doctype":"ToDo", "description": "test"})
todo.insert(ignore_permissions=True)
todo.submit()
---
#### 4. Insert a document on saving of another document
class MyType(Document):
def on_update(self):
todo = frappe.get_doc({"doctype":"ToDo", "description": "test"})
todo.insert()
----
#### 5. Insert a document with child tables:
sales_order = frappe.get_doc({
"doctype": "Sales Order",
"company": "_Test Company",
"customer": "_Test Customer",
"delivery_date": "2013-02-23",
"sales_order_details": [
{
"item_code": "_Test Item Home Desktop 100",
"qty": 10.0,
"rate": 100.0,
"warehouse": "_Test Warehouse - _TC"
}
]
})
sales_order.insert()

View file

@ -0,0 +1,89 @@
# Overriding Link Query By Custom Script
You can override the standard link query by using `set_query`
### 1. Adding Fitlers
You can add filters to the query:
frappe.ui.form.on("Bank Reconciliation", "onload", function(frm) {
cur_frm.set_query("bank_account", function() {
return {
"filters": {
"account_type": "Bank",
"group_or_ledger": "Ledger"
}
};
});
});
A more complex query:
frappe.ui.form.on("Bank Reconciliation", "onload", function(frm){
cur_frm.set_query("bank_account", function(){
return {
"filters": [
["Bank Account": "account_type", "=", "Bank"],
["Bank Account": "group_or_ledger", "!=", "Group"]
]
}
});
});
---
### 2. Calling a Different Method to Generate Results
You can also set a server side method to be called on the query:
frm.set_query("item_code", "items", function() {
return {
query: "erpnext.controllers.queries.item_query",
filters: frm.doc.enquiry_type === "Maintenance" ?
{"is_service_item": "Yes"} : {"is_sales_item": "Yes"}
};
});
#### Custom Method
The custom method should return a list of items for auto select. If you want to send additional data, you can send multiple columns in the list.
Parameters to the custom method are:
`def custom_query(doctype, txt, searchfield, start, page_len, filters)`
**Example:**
# searches for leads which are not converted
def lead_query(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""select name, lead_name, company_name from `tabLead`
where docstatus &lt; 2
and ifnull(status, '') != 'Converted'
and ({key} like %(txt)s
or lead_name like %(txt)s
or company_name like %(txt)s)
{mcond}
order by
if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
if(locate(%(_txt)s, lead_name), locate(%(_txt)s, lead_name), 99999),
if(locate(%(_txt)s, company_name), locate(%(_txt)s, company_name), 99999),
name, lead_name
limit %(start)s, %(page_len)s""".format(**{
'key': searchfield,
'mcond':get_match_cond(doctype)
}), {
'txt': "%%%s%%" % txt,
'_txt': txt.replace("%", ""),
'start': start,
'page_len': page_len
})
For more examples see:
[https://github.com/frappe/erpnext/blob/develop/erpnext/controllers/queries.py](https://github.com/frappe/erpnext/blob/develop/erpnext/controllers/queries.py)
<!-- markdown -->

View file

@ -0,0 +1,47 @@
# Running Background Jobs
Sometimes you may not want a user request to be executed immediately but added to a queue that will be executed by a background worker. The advantage of doing this is that your web workers remain free to execute other requests and longer jobs do not eat up all of your resources.
From version 7, Frappé uses Python RQ to run background jobs.
To enqueue a job,
from frappe.jobs.background_jobs import enqueue
def long_job(arg1, arg2):
frappe.publish_realtime('msgprint', 'Starting long job...')
# this job takes a long time to process
frappe.publish_realtime('msgprint', 'Ending long job...')
def enqueue_long_job(arg1, args2):
enqueue('myapp.mymodule.long_job', arg1=arg1, arg2=arg2)
This will enqueue to the queue `default`
other queues are `worker_long` and `worker_short`
#### Called delayed actions on Document objects
You can also called delayed actions on document objects, for example in Stock Reconciliation if there are more than 100 items, it is executed as a background job.
Example: you can call `doc.queue_action('submit')`
Note: This only works for `save`, `submit`, `cancel`
You can also push certain actions to the background if you anticipate the execution is very large.
For example:
def submit(self):
if len(self.items) > 100:
self.queue_action('submit')
else:
self._submit()
#### Debugging
If you are on `bench start`
You will see logs in your terminal.
Note: default worker does not auto restart, so you will have to kill bench and start again after you make changes.

View file

@ -0,0 +1,11 @@
# Single Type Doctype
DocTypes have a table associated with them. For example DocType **Customer** will have a table `tabCustomer` associated with it.
**Single** type DocTypes have no table associated and there is only one Document for it. This is similar to the Singleton pattern in Java. Single DocTypes are ideal for saving Settings (that are globally applicable) and for wizard / helper type forms that have no documents, but when the DocType is used for the Form UI.
The data in Single DocType is stored in `tabSingles` (`doctype`, `field`, `value`)
#### Examples
In Frappé, Single types are **System Settings** and **Customize Form**

View file

@ -0,0 +1,49 @@
# Trigger Event On Deletion Of Grid Row
To trigger an event when a row from a Child Table has been deleted (when user clicks on `delete` button), you need to add a handler the `fieldname_remove` event to Child Table, where fieldname is the fieldname of the Child Table in Parent Table declaration.
For example:
Assuming that your parent DocType is named `Item` has a Table Field linked to `Item Color` DocType with decloration name `color`.
In order to "catch" the delete event:
frappe.ui.form.on('Item Color', {
color_remove: function(frm) {
// You code here
// If you console.log(frm.doc.color) you will get the remaining color list
}
);
The same process is used to trigger the add event (when user clicks on `add row` button):
frappe.ui.form.on('Item Color', {
color_remove: function(frm) {
// You code here
// If you console.log(frm.doc.color) you will get the remaining color list
},
color_add: function(frm) {
}
});
Notice that the handling is be made on Child DocType Table `form.ui.on` and not on Parent Doctype so a minimal full example is:
```javascript
frappe.ui.form.on('Item',{
// Your client side handling for Item
});
frappe.ui.form.on('Item Color', {
color_remove: function(frm) {
// Deleting is triggered here
}
);
```
Handlers are:
1. fieldname_add
1. fieldname_move
1. fieldname_before_remove
1. fieldname_remove

View file

@ -0,0 +1,43 @@
# Using Html Templates In Javascript
Often while building javascript interfaces, there is a need to render DOM as an HTML template. Frappé Framework uses John Resig's Microtemplate script to render HTML templates in the Desk application.
> Note 1: In Frappé we use the Jinja-like `{% raw %}{%{% endraw %}` tags to embed code rather than the standard `<%`
> Note 2: Never use single quotes `'` inside the HTML template.
To render a template,
1. Create a template `html` file in your app. e.g. `address_list.html`
1. Add it to `build.json` for your app (you can include it in `frappe.min.js` or your own javascript file).
1. To render it in your app, use `frappe.render(frappe.templates.address_list, {[context]})`
#### Example Template:
From `erpnext/public/js/templates/address_list.js`
{% raw %}<p><button class="btn btn-sm btn-default btn-address">
<i class="fa fa-plus"></i> New Address</button></p>
{% for(var i=0, l=addr_list.length; i<l; i++) { %}
<hr>
<a href="#Form/Address/{%= addr_list[i].name %}" class="btn btn-sm btn-default pull-right">
{%= __("Edit") %}</a>
<h4>{%= addr_list[i].address_type %}</h4>
<div style="padding-left: 15px;">
<div>
{% if(addr_list[i].is_primary_address) { %}<span class="label label-info">
{%= __("Primary") %}</span>{% } %}
{% if(addr_list[i].is_shipping_address) { %}<span class="label label-default">
{%= __("Shipping") %}</span>{% } %}
</div>
<p style="margin-top: 5px;">{%= addr_list[i].display %}</p>
</div>
{% } %}
{% if(!addr_list.length) { %}
<p class="text-muted">{%= __("No address added yet.") %}</p>
{% } %}{% endraw %}
<!-- markdown -->

View file

@ -0,0 +1,7 @@
# Automated Testing
Frappé Provides you a test framework to write and execute tests that can be run directly on a Continuous Integration Tool like Travis
You can write server-side unit tests or UI tests
{index}

View file

@ -0,0 +1,3 @@
unit-testing
integration-testing
qunit-testing

View file

@ -0,0 +1,49 @@
# UI Integration Testing
You can write integration tests using the Selenium Driver. `frappe.utils.selenium_driver` gives you a friendly API to write selenium based tests
To write integration tests, create a standard test case by creating a python file starting with `test_`
All integration tests will be run at the end of the unittests.
### Example
Here is an example of an integration test to check insertion of a To Do
from __future__ import print_function
from frappe.utils.selenium_testdriver import TestDriver
import unittest
import time
class TestToDo(unittest.TestCase):
def setUp(self):
self.driver = TestDriver()
def test_todo(self):
self.driver.login()
# list view
self.driver.set_route('List', 'ToDo')
time.sleep(2)
# new
self.driver.click_primary_action()
time.sleep(2)
# set input
self.driver.set_text_editor('description', 'hello')
# save
self.driver.click_modal_primary_action()
time.sleep(2)
self.assertTrue(self.driver.get_visible_element('.result-list')
.find_element_by_css_selector('.list-item')
.find_element_by_css_selector('.list-id').text=='hello')
def tearDown(self):
self.driver.close()

View file

@ -0,0 +1,75 @@
# UI Testing with Frappé API
You can either write integration tests, or directly write tests in Javascript using [QUnit](http://api.qunitjs.com/)
QUnit helps you write UI tests using the UQuit framework and native frappe API. As you might have guessed, this is a much faster way of writing tests.
### Test Runner
To write QUnit based tests, add your tests in the `tests/ui` folder of your application. Your test files must begin with `test_` and end with `.js` extension.
To run your files, you can use the **Test Runner**. The **Test Runner** gives a user interface to load all your QUnit tests and run them in the browser.
In the CI, all QUnit tests are run by the **Test Runner** using `frappe/tests/test_test_runner.py`
<img src="/docs/assets/img/app-development/test-runner.png" class="screenshot">
### Running Tests
To run a Test Runner based test, use the `run-ui-tests` bench command by passing the name of the file you want to run.
bench run-ui-tests --test frappe/tests/ui/test_list.js
This will pass the filename to `test_test_runner.py` that will load the required JS in the browser and execute the tests
### Debugging Tests
To debug a test, you can open it in the **Test Runner** from your UI and run it manually to see where it is exactly failing.
### Test Sequence
In Frappé UI tests are run in a fixed sequence to ensure dependencies.
The sequence in which the tests will be run will be in `tests/ui/tests.txt`
file.
### Running All UI Tests
To run all UI tests together for your app run
bench run-ui-tests --app [app_name]
This will run all the files in your `tests/ui` folder one by one.
### Example QUnit Test
Here is the example of the To Do test in QUnit
QUnit.test("Test quick entry", function(assert) {
assert.expect(2);
let done = assert.async();
let random_text = frappe.utils.get_random(10);
frappe.run_serially([
() => frappe.set_route('List', 'ToDo'),
() => frappe.new_doc('ToDo'),
() => frappe.quick_entry.dialog.set_value('description', random_text),
() => frappe.quick_entry.insert(),
(doc) => {
assert.ok(doc && !doc.__islocal);
return frappe.set_route('Form', 'ToDo', doc.name);
},
() => assert.ok(cur_frm.doc.description.includes(random_text)),
// Delete the created ToDo
() => frappe.tests.click_page_head_item('Menu'),
() => frappe.tests.click_dropdown_item('Delete'),
() => frappe.tests.click_page_head_item('Yes'),
() => done()
]);
});
### Writing Test Friendly Code with Promises
Promises are a great way to write test-friendly code. If your method calls an aysnchronous call (ajax), then you should return an `Promise` object. While writing tests, if you encounter a function that does not return a `Promise` object, you should update the code to return a `Promise` object.

View file

@ -0,0 +1,199 @@
# Unit Testing
## 1.Introduction
Frappé provides some basic tooling to quickly write automated tests. There are some basic rules:
1. Test can be anywhere in your repository but must begin with `test_` and should be a `.py` file.
1. Tests must run on a site that starts with `test_`. This is to prevent accidental loss of data.
1. Test stubs are automatically generated for new DocTypes.
1. Frappé test runner will automatically build test records for dependant DocTypes identified by the `Link` type field (Foreign Key)
1. Tests can be executed using `bench run-tests`
1. For non-DocType tests, you can write simple unittests and prefix your file names with `test_`.
## 2. Running Tests
This function will build all the test dependencies and run your tests.
You should run tests from "frappe_bench" folder. Without options all tests will be run.
bench run-tests
If you need more information about test execution - you can use verbose log level for bench.
bench --verbose run-tests
### Options:
--app <AppName>
--doctype <DocType>
--test <SpecificTest>
--module <Module> (Run a particular module that has tests)
--profile (Runs a Python profiler on the test)
--junit-xml-output<PathToXML> (The command provides test results in the standard XUnit XML format)
#### 2.1. Example for app:
All applications are located in folder: "~/frappe-bench/apps".
We can run tests for each application.
- frappe-bench/apps/erpnext/
- frappe-bench/apps/erpnext_demo/
- frappe-bench/apps/frappe/
bench run-tests --app erpnext
bench run-tests --app erpnext_demo
bench run-tests --app frappe
#### 2.2. Example for doctype:
frappe@erpnext:~/frappe-bench$ bench run-tests --doctype "Activity Cost"
.
----------------------------------------------------------------------
Ran 1 test in 0.008s
OK
#### 2.3. Example for test:
Run a specific case in User:
frappe@erpnext:~/frappe-bench$ bench run-tests --doctype User --test test_get_value
.
----------------------------------------------------------------------
Ran 1 test in 0.005s
OK
#### 2.4. Example for module:
If we want to run tests in the module:
/home/frappe/frappe-bench/apps/erpnext/erpnext/support/doctype/issue/test_issue.py
We should use module name like this (related to application folder)
erpnext.support.doctype.issue.test_issue
#####EXAMPLE:
frappe@erpnext:~/frappe-bench$ bench run-tests --module "erpnext.stock.doctype.stock_entry.test_stock_entry"
...........................
----------------------------------------------------------------------
Ran 27 tests in 30.549s
#### 2.5. Example for profile:
frappe@erpnext:~/frappe-bench$ bench run-tests --doctype "Activity Cost" --profile
.
----------------------------------------------------------------------
Ran 1 test in 0.010s
OK
9133 function calls (8912 primitive calls) in 0.011 seconds
Ordered by: cumulative time
ncalls tottime percall cumtime percall filename:lineno(function)
2 0.000 0.000 0.008 0.004 /home/frappe/frappe-bench/apps/frappe/frappe/model/document.py:187(insert)
1 0.000 0.000 0.003 0.003 /home/frappe/frappe-bench/apps/frappe/frappe/model/document.py:386(_validate)
13 0.000 0.000 0.002 0.000 /home/frappe/frappe-bench/apps/frappe/frappe/database.py:77(sql)
255 0.000 0.000 0.002 0.000 /home/frappe/frappe-bench/apps/frappe/frappe/model/base_document.py:91(get)
12 0.000 0.000 0.002 0.000
#### 2.6. Example for XUnit XML:
##### How to run:
bench run-tests --junit-xml-output=/reports/junit_test.xml
##### Example of test report:
<testsuite tests="3">
<testcase classname="foo1" name="ASuccessfulTest"/>
<testcase classname="foo2" name="AnotherSuccessfulTest"/>
<testcase classname="foo3" name="AFailingTest">
<failure type="NotEnoughFoo"> details about failure </failure>
</testcase>
</testsuite>
Its designed for the CI Jenkins, but will work for anything else that understands an XUnit-formatted XML representation of test results.
#### Jenkins configuration support:
1. You should install xUnit plugin - https://wiki.jenkins-ci.org/display/JENKINS/xUnit+Plugin
2. After installation open Jenkins job configuration, click the box named “Publish JUnit test result report” under the "Post-build Actions" and enter path to XML report:
(Example: _reports/*.xml_)
## 3. Tests for a DocType
### 3.1. Writing DocType Tests:
1. Records that are used for testing are stored in a file `test_records.json` in the doctype folder. [For example see the Event Tests](https://github.com/frappe/frappe/blob/develop/frappe/core/doctype/event/test_records.json).
1. Test cases are in a file named `test_[doctype].py`
1. To provide the test records (and dependencies) call `test_records = frappe.get_test_records('Event')` in your test case file.
#### Example (for `test_records.json`):
[
{
"doctype": "Event",
"subject":"_Test Event 1",
"starts_on": "2014-01-01",
"event_type": "Public"
},
{
"doctype": "Event",
"starts_on": "2014-01-01",
"subject":"_Test Event 2",
"event_type": "Private"
},
{
"doctype": "Event",
"starts_on": "2014-01-01",
"subject":"_Test Event 3",
"event_type": "Private",
"event_individuals": [{
"person": "test1@example.com"
}]
}
]
#### Example (for `test_event.py`):
# Copyright (c) 2015, Frappé Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
import frappe
import frappe.defaults
import unittest
# load test records and dependencies
test_records = frappe.get_test_records('Event')
class TestEvent(unittest.TestCase):
def tearDown(self):
frappe.set_user("Administrator")
def test_allowed_public(self):
frappe.set_user("test1@example.com")
doc = frappe.get_doc("Event", frappe.db.get_value("Event", {"subject":"_Test Event 1"}))
self.assertTrue(frappe.has_permission("Event", doc=doc))
def test_not_allowed_private(self):
frappe.set_user("test1@example.com")
doc = frappe.get_doc("Event", frappe.db.get_value("Event", {"subject":"_Test Event 2"}))
self.assertFalse(frappe.has_permission("Event", doc=doc))
def test_allowed_private_if_in_event_user(self):
frappe.set_user("test1@example.com")
doc = frappe.get_doc("Event", frappe.db.get_value("Event", {"subject":"_Test Event 3"}))
self.assertTrue(frappe.has_permission("Event", doc=doc))
def test_event_list(self):
frappe.set_user("test1@example.com")
res = frappe.get_list("Event", filters=[["Event", "subject", "like", "_Test Event%"]], fields=["name", "subject"])
self.assertEquals(len(res), 2)
subjects = [r.subject for r in res]
self.assertTrue("_Test Event 1" in subjects)
self.assertTrue("_Test Event 3" in subjects)
self.assertFalse("_Test Event 2" in subjects)

View file

@ -0,0 +1,109 @@
# Frappé Apps
Frappé Apps are Python packages which use the Frappé platform. They can live
anywhere on the [Python
path](https://docs.python.org/2/tutorial/modules.html#the-module-search-path)
and must have an entry in the `apps.txt` file.
### Creating an app
Frappé ships with a boiler plate for a new app. The command `bench make-app
app-name` helps you start a new app by starting an interactive shell.
% bench new-app sample_app
App Name: sample_app
App Title: Sample App
App Description: This is a sample app.
App Publisher: Acme Inc.
App Icon: icon-linux
App Color: #6DAFC9
App Email: info@example.com
App URL: http://example.com
App License: MIT
The above command would create an app with the following directory structure.
sample_app
├── license.txt
├── MANIFEST.in
├── README.md
├── sample_app
│   ├── __init__.py
│   ├── sample_app
│   │   └── __init__.py
│   ├── config
│   │   ├── desktop.py
│   │   └── __init__.py
│   ├── hooks.py
│   ├── modules.txt
│   ├── patches.txt
│   └── templates
│   ├── generators
│   │   └── __init__.py
│   ├── __init__.py
│   ├── pages
│   │   └── __init__.py
│   └── statics
└── setup.py
Here, "App Icon" is a font awesome class that you can select from
[http://fortawesome.github.io/Font-Awesome/icons/](http://fortawesome.github.io/Font-Awesome/icons/).
The boiler plate contains just enough files to show your app icon on the [Desk].
### Files in the app
#### `hooks.py`
The `hooks.py` file defines the metadata of your app and integration points
with other parts of Frappé or Frappé apps. Examples of such parts include task
scheduling or listening to updates to different documents in the system. For
now, it just contains the details you entered during app creation.
app_name = "sample-app"
app_title = "Sample App"
app_publisher = "Acme Inc."
app_description = "This is a sample app."
app_icon = "fa-linux"
app_color = "black"
app_email = "info@example.com"
app_url = "http://example.com"
app_version = 0.0.1
#### `modules.txt`
Modules in Frappé help you organize Documents in Frappé and they are defined in
the `modules.txt` file in your app. It is necessary for every [DocType] to be
attached to a module. By default a module by the name of your app is added.
Also, each module gets an icon on the [Desk]. For example, the [ERPNext] app is
organized in the following modules.
accounts
buying
home
hr
manufacturing
projects
selling
setup
stock
support
utilities
contacts
### Adding app to a site
Once you have an app, whether it's the one you just created or any other you
downloaded, you are required to do the following things.
Download the app via git
bench get-app https://github.com/org/app_name
Install the app to your site
bench --site site_name install-app app_name

View file

@ -0,0 +1,78 @@
# Frappé Ajax Call
In Frappé Framework, you can manage ajax calls via frappe.call. The frappe.call works in asynchronous manner ie. send requests and handle response via callback mechanism.
## frappe.call Structure
frappe.call({
type: opts.type || "POST",
args: args,
success: callback,
error: opts.error,
always: opts.always,
btn: opts.btn,
freeze: opts.freeze,
freeze_message: opts.freeze_message,
async: opts.async,
url: opts.url || frappe.request.url,
})
#### Parameter description :
- type: String parameter, http request type "GET", "POST", "PUT", "DELETE". Default set to "POST".
- args: associative array, arguments that will pass with request.
- success: Function parameter, code snippet, will after successful execution of request
- error: Function parameter, code snippet, will execute after request failure
- always: Function parameter, code snipper, will execute in either case
- btn: Object parameter, triggering object
- freeze: Boolean parameter, if set freeze the instance util it receives response
- freeze_message: String parameter, message will populate to screen while screen is in freeze state.
- async: Boolean parameter, default set to true. So each frappe.call is asynchronous. To make call synchronous set parameter value as false
- url: String parameter, location from where hitting the request
## How to use frappe.call ?
### Calling standard API
frappe.call({
method: 'frappe.client.get_value',
args: {
'doctype': 'Item',
'filters': {'name': item_code},
'fieldname': [
'item_name',
'web_long_description',
'description',
'image',
'thumbnail'
]
},
callback: function(r) {
if (!r.exc) {
// code snippet
}
}
});
- Param description:
- doctype: name of doctype for which you want to pull information
- filters: condition specifier
- fieldname: you can specify fields in array that you want back in response
### Calling whitelisted functions
- Code client side
frappe.call({
method: "frappe.core.doctype.user.user.get_all_roles", //dotted path to server method
callback: function(r) {
// code snippet
}
})
- Code at server side
@frappe.whitelist()
def get_all_roles():
// business logic
return value
Note: While accessing any server side method via frappe.call(), you need to whitelist server side method using decorator `@frappe.whitelist`

View file

@ -0,0 +1,277 @@
# Hooks
<!-- TODO: Add tables for quick reference -->
Hooks are the duct tape of the Frappé system. Hooks allow you to "hook" in to
functionality and events of other parts of the Frappé system. Following are the
official hooks from Frappé.
### Application Name and Details
1. `app_name` - slugified name with underscores e.g. "shopping\_cart"
2. `app_title` - full title name e.g. "Frappé"
3. `app_publisher`
4. `app_description`
5. `app_version`
6. `app_icon` - font-awesome icon or image url
7. `app_color` - hex colour background of the app icon
### Install Events
1. `before_install`
2. `after_install`
The above hooks are called before and after installation of the app they are in.
For example, [ERPNext](/apps/erpnext)'s hooks contains a line,
after_install = "erpnext.setup.install.after_install"
So, the function after\_install is imported and called after ERPNext is installed.
Note, the `before_install` and `after_install` hooks are called with no arguments.
### Boot Session
After a successful login, the Frappé JS Client requests for a resource called
`bootinfo`. The `bootinfo` is available as a global in Javascript via
`frappe.boot`. By default, the `bootinfo` contains
* System defaults
* Notification status
* Permissions
* List of icons on desktop
* User settings
* Language and timezone info
If your app wants to modify bootinfo, it can declare a hook `boot_session`. The
value is assumed to be a dotted path to a function and is called with one
argument, bootinfo which it can modify and return.
Eg,
boot_session = "erpnext.startup.boot.boot_session"
### Notification configurations
The notification configuration hook is expected to return a Python dictionary.
{
"for_doctype": {
"Issue": {"status":"Open"},
"Customer Issue": {"status":"Open"},
},
"for_module_doctypes": {
"ToDo": "To Do",
"Event": "Calendar",
"Comment": "Messages"
},
"for_module": {
"To Do": "frappe.core.notifications.get_things_todo",
"Calendar": "frappe.core.notifications.get_todays_events",
"Messages": "frappe.core.notifications.get_unread_messages"
}
}
The above configuration has three parts,
1. `for_doctype` part of the above configuration marks any "Issue"
or "Customer Issue" as unread if its status is Open
2. `for_module_doctypes` maps doctypes to module's unread count.
3. `for_module` maps modules to functions to obtain its unread count. The
functions are called without any argument.
### Javascript / CSS Assets
The following hooks allow you to bundle built assets to your app for serving.
There are two types of assets, app and web. The app assets are loaded in the
Desk and web assets are loaded in the website.
1. `app_include_js`
2. `app_include_css`
3. `web_include_js`
4. `web_include_css`
Eg,
app_include_js = "assets/js/erpnext.min.js"
web_include_js = "assets/js/erpnext-web.min.js"
Note: to create an asset bundle (eg, assets/js/erpnext.min.js) the target file
should be in build.json of your app.
### Configuring Reports
In the report view, you can force filters as per doctype using `dump_report_map`
hook. The hook should be a dotted path to a Python function which will be called
without any arguments. Example of output of this function is below.
"Warehouse": {
"columns": ["name"],
"conditions": ["docstatus < 2"],
"order_by": "name"
}
Here, for a report with Warehouse doctype, would include only those records that
are not cancelled (docstatus < 2) and will be ordered by name.
### Modifying Website Context
Context used in website pages can be modified by adding
a `update_website_context` hook. This hook should be a dotted path to a function
which will be called with a context (dictionary) argument.
### Customizing Email footer
By default, for every email, a footer with content, "Sent via Frappé" is sent.
You can customize this globally by adding a `mail_footer` hook. The hook should
be a dotted path to a variable.
### Session Creation Hook
You can attach custom logic to the event of a successful login using
`on_session_creation` hook. The hook should be a dotted path to a Python
function that takes login\_manager as an argument.
Eg,
def on_session_creation(login_manager):
"""make feed"""
if frappe.session['user'] != 'Guest':
# log to timesheet
pass
### Website Clear Cache
If you cache values in your views, the `website_clear_cache` allows you to hook
methods that invalidate your caches when Frappé tries to clear cache for all
website related pages.
### Document hooks
#### Permissions
#### Query Permissions
You can customize how permissions are resolved for a DocType by hooking custom
permission match conditions using the `permission_query_conditions` hook. This
match condition is expected to be fragment for a where clause in an sql query.
Structure for this hook is as follows.
permission_query_conditions = {
"{doctype}": "{dotted.path.to.function}",
}
The output of the function should be a string with a match condition.
Example of such a function,
def get_permission_query_conditions():
return "(tabevent.event_type='public' or tabevent.owner='{user}'".format(user=frappe.session.user)
The above function returns a fragment that permits an event to listed if it's
public or owned by the current user.
#### Document permissions
You can hook to `doc.has_permission` for any DocType and add special permission
checking logic using the `has_permission` hook. Structure for this hook is,
has_permission = {
"{doctype}": "{dotted.path.to.function}",
}
The function will be passed the concerned document as an argument. It should
True or a falsy value after running the required logic.
For Example,
def has_permission(doc):
if doc.event_type=="Public" or doc.owner==frappe.session.user:
return True
The above function permits an event if it's public or owned by the current user.
#### CRUD Events
You can hook to various CRUD events of any doctype, the syntax for such a hook
is as follows,
doc_events = {
"{doctype}": {
"{event}": "{dotted.path.to.function}",
}
}
To hook to events of all doctypes, you can use the follwing syntax also,
doc_events = {
"*": {
"on_update": "{dotted.path.to.function}",
}
}
The hook function will be passed the doc in concern as the only argument.
##### List of events
* `validate`
* `before_save`
* `autoname`
* `after_save`
* `before_insert`
* `after_insert`
* `before_submit`
* `before_cancel`
* `before_update_after_submit`
* `on_update`
* `on_submit`
* `on_cancel`
* `on_update_after_submit`
* `on_change`
* `on_trash`
* `after_delete`
Eg,
doc_events = {
"Cab Request": {
"after_insert": topcab.schedule_cab",
}
}
### Scheduler Hooks
Scheduler hooks are methods that are run periodically in background. Structure for such a hook is,
scheduler_events = {
"{event_name}": [
"{dotted.path.to.function}"
],
}
#### Events
* `daily`
* `daily_long`
* `weekly`
* `weekly_long`
* `monthly`
* `monthly_long`
* `hourly`
* `all`
The scheduler events require celery, celerybeat and redis (or a supported and
configured broker) to be running. The events with suffix '\_long' are for long
jobs. The `all` event is triggered everytime (as per the celerybeat interval).
Example,
scheduler_events = {
"{daily}": [
"erpnext.accounts.doctype.sales_invoice.sales_invoice.manage_recurring_invoices"
],
"{daily_long}": [
"erpnext.setup.doctype.backup_manager.backup_manager.take_backups_daily"
],
}

View file

@ -0,0 +1,3 @@
# Basics
{index}

View file

@ -0,0 +1,8 @@
install
apps
sites
site_config
hooks
translations
writing-tests
frappe_ajax_call

View file

@ -0,0 +1,10 @@
# Installing Frappé
## Frappé bench
The following steps help you setup an isolated environment (bench) to run and
develop Frappé apps. A virtualenv is installed in the env directory. You can
activate it by running `source ./env/bin/activate` or use execute using
absolute/relative path (eg, `./env/bin/frappe`).
For more info, see [Frappé Bench](https://github.com/frappe/bench/)

View file

@ -0,0 +1,52 @@
# Site Config
Settings for `sites/[site]/site_config.json`
`site_config.json` stores global settings for a particular site and is present in the site directory. Here is a list of properties you can set in `site_config.json`.
Example:
{
"db_name": "test_frappe",
"db_password": "test_frappe",
"admin_password": "admin",
}
### Mandatory Settings
- `db_name`: Database Name.
- `db_password`: Database password.
- `encryption_key`: encryption_key for stored non user passwords.
### Optional Settings
- `admin_password`: Default Password for "Administrator".
- `mute_emails`: Stops email sending if true.
- `deny_multiple_logins`: Stop users from having more than one active session.
- `root_password`: MariaDB root password.
### Remote Database Host Settings
- `db_host`: Database host if not `localhost`.
To connect to a remote database server using ssl, you must first configure the database host to accept SSL connections. An example of how to do this is available at https://www.digitalocean.com/community/tutorials/how-to-configure-ssl-tls-for-mysql-on-ubuntu-16-04. After you do the configuration, set the following three options. All options must be set for Frappé to attempt to connect using SSL.
- `db_ssl_ca`: Full path to the ca.pem file used for connecting to a database host using ssl. Example value is `"/etc/mysql/ssl/ca.pem"`.
- `db_ssl_cert`: Full path to the cert.pem file used for connecting to a database host using ssl. Example value is `"/etc/mysql/ssl/client-cert.pem"`.
- `db_ssl_key`: Full path to the key.pem file used for connecting to a database host using ssl. Example value is `"/etc/mysql/ssl/client-key.pem"`.
### Default Outgoing Email Settings
- `mail_server`: SMTP server hostname.
- `mail_port`: STMP port.
- `use_ssl`: Connect via SSL / TLS.
- `mail_login`: Login id for SMTP server.
- `mail_password`: Password for SMTP server.
### Developer Settings
- `developer_mode`: If developer mode is set, DocType changes are automatically updated in files.
- `disable_website_cache`: Don't cache website pages.
- `logging`: writes logs if **1**, writes queries also if set to **2**.
### Others
- `robots_txt`: Path to robots.txt file to be rendered when going to frappe-site.com/robots.txt

View file

@ -0,0 +1,82 @@
# Sites
## Sites Directory
Frappé is a multitenant platform and each tenant is called a site. Sites exist
in a directory called `sites_dir`, assumed as the current working directory when
running a frappe command or other services like Celery worker or a WSGI server.
You can set `sites_dir` with an environment variable `SITES_DIR` or pass
`--sites_dir` option to the frappe command.
Apart from the sites, the `sites_dir` should contain the following.
#### apps.txt
`apps.txt` contain a list of Python packages to treat as Frappé apps. Every
frappe app that you intend to use in you site should have an entry in this file.
Also, they should be in the `PYTHONPATH`. For more information, refer
[Frappé Apps](/help/apps).
#### common\_site\_config.json
`common_site_config.json` is an optional file. Configuration common to all sites
can be put in this file.
#### assets
Assets contain files that are required to be served for the browser client.
These generally include *.js, *.css, *.png files. This directory is auto
generated using the `bench build` command.
#### languages.txt
`languages.txt` is an autogenerated file which maps every language to it's code.
## Site
A site is a directory in `sites_dir` which represents a tenant in Frappé Platform.
### Directory Structure
site
├── locks
├── private
│   └── backups
├── public
│   └── files
│ └── testfile.txt
└── site_config.json
* `locks` directory is used by the scheduler to synchronize various jobs using
the [file locking concept](http://en.wikipedia.org/wiki/File_locking).
* `private` directory contains files that require authentication to access.
Presently, it is limited only to backups.
* `public` directory contains files that can directly served. In the above
example, `testfile.txt` can be accessed by the URL,
http://site/files/testfile.txt
* `site_config.json` contains site specific configuration
### Site Config
[See configuration options for `site_config.json`](/frappe/user/en/guides/basics/site_config)
### Site Resolution
While responding to an HTTP request, a site is automatically selected based on,
* `Host` header in the HTTP request matches a site
* `X-Frappé-Site-Name` header in the HTTP request matches a site
It is also possible to force the development server to serve a specific site by
starting it with the following command.
`bench --site SITENAME serve`
### Adding a new site
`bench new-site SITENAME`

View file

@ -0,0 +1,88 @@
# Translations
<!-- jinja -->
<!-- static -->
This document shows how to translations are managed in ERPNext and how to add
a new language or update translations of an existing language.
### 1. Source
Translatable text exists in 3 main sources:
1. Javascript Code Files (both framework and application)
2. Python Code Files
3. DocTypes (names, labels and select options)
#### Strings in Code Files
Strings in code files are annotated using the `_` (underscore) method
1. In Python it is the `frappe._` method. Example:
`frappe._("String {0} must be translated".format(txt))`
2. In Javascript it is the `__` method. Example:
`__("String {0} must be translated", [txt])`
**Note:** If you find translatable strings are not properly annotated using the `_`
method, you can add them in the code and rebuild the translations.
### 2. How Translations Are Picked up During Execution
Whenever a translation is called via the _ method, the entire translation
dictionaries from all apps are built and stored in memcache.
Based on the user preferences or request preferences, the appropriate
translations are loaded at the time of request on the server side. Or if
metadata (DocType) is queried, then the appropriate translations are appended
when the DocType data is requested.
The underscore `_` method will replace the strings based on the available
translations loaded at the time.
### 3. Adding New Translations
1. To find untranslated strings, run `bench get-untranslated [lang] [path]`
1. Add the translated strings in another file in the same order
1. run `bench update-translations [lang] [path of untranslated strings] [path of translated strings]`
### 4. Improving Translations:
For updating translations, please go to the to [the translation portal](https://frappe.io/translator).
If you want to do it directly via code:
To improve an existing translation, just edit the master translation files in
the `translations` of each app
> Please contribute your translations back to ERPNext by sending us a Pull
Request.
### 5. Bootstrapping a New Language
If you want to add a new language it is similar to adding new translations. You need to first export all the translations strings in one file, get them translated via Google Translate Tool or Bing Translate Tool and then import the translations into individual apps.
**Step 1: Export to a file**
$ bench get-untranslated [lang] [path]
**Step 2: Translate**
Create another file with updated translations (in the same order as the source file). For this you can use the [Google Translator Toolkit](https://translate.google.com/toolkit) or [Bing Translator](http://www.bing.com/translator/).
**Step 3: Import your translations**
$ bench update-translations [lang] [source path] [translated path]
**Step 4: Update `languages.txt`**
Add your language in `apps/languages.txt` and also `frappe/data/languages.txt` (fore new bench installs)
**Step 5: Commit each app and push**
A new file will be added to the `translations` folder in each app. You need to add that file and push to your repo. Then send us a pull-request.
---

View file

@ -0,0 +1,25 @@
# Import Large Csv File
To import very large CSV files, you can use the bench utility `import-csv`.
The benefit is that this is not subject to timeouts if you use the web interface.
Here is an example:
bench --site test.erpnext.com import-csv ~/Downloads/Activity_Type.csv
### Help
$ bench import-csv --help
Usage: bench import-csv [OPTIONS] PATH
Import CSV using data import tool
Options:
--only-insert Do not overwrite existing records
--submit-after-import Submit document after importing it
--ignore-encoding-errors Ignore encoding errors while coverting to unicode
--help Show this message and exit.
<!-- markdown -->

View file

@ -0,0 +1,3 @@
# Data Management
{index}

View file

@ -0,0 +1,2 @@
import-large-csv-file
using-data-migration-tool

View file

@ -0,0 +1,99 @@
# Using the Data Migration Tool
> Data Migration Tool was introduced in Frappé Framework version 9.
The Data Migration Tool was built to abstract all the syncing of data between a remote source and a DocType. This is a middleware layer between your Frappé based website and a remote data source.
To understand this tool, let's make a connector to push ERPNext Items to an imaginary service called Atlas.
### Data Migration Plan
A Data Migration Plan encapsulates a set of mappings.
Let's make a new *Data Migration Plan*. Set the plan name as 'Atlas Sync'. We also need to add mappings in the mappings child table.
<img class="screenshot" alt="New Data Migration Plan" src="/docs/assets/img/data-migration/new-data-migration-plan.png">
### Data Migration Mapping
A Data Migration Mapping is a set of rules that specify field-to-field mapping.
Make a new *Data Migration Mapping*. Call it 'Item to Atlas Item'.
To define a mapping, we need to put in some values that define the structure of local and remote data.
1. Remote Objectname: A name that identifies the remote object e.g Atlas Item
1. Remote primary key: This is the name of the primary key for Atlas Item e.g id
1. Local DocType: The DocType which will be used for syncing e.g Item
1. Mapping Type: A Mapping can be of type 'Push' or 'Pull', depending on whether the data is to be mapped remotely or locally. It can also be 'Sync', which will perform both push and pull operations in a single cycle.
1. Page Length: This defines the batch size of the sync.
<img class="screenshot" alt="New Data Migration Mapping" src="/docs/assets/img/data-migration/new-data-migration-mapping.png">
#### Specifying field mappings:
The most basic form of a field mapping would be to specify fieldnames of the remote and local object. However, if the mapping is one-way (push or pull), the source field name can also take literal values in quotes (for e.g `"GadgetTech"`) and eval statements (for e.g `"eval:frappe.db.get_value('Company', 'Gadget Tech', 'default_currency')"`). For example, in the case of a push mapping, the local fieldname can be set to a string in quotes or an `eval` expression, instead of a field name from the local doctype. (This is not possible with a sync mapping, where both local and remote fieldnames serve as a target destination at a some point, and thus cannot be a literal value).
Let's add the field mappings and save:
<img class="screenshot" alt="Add fields in Data Migration Mapping" src="/docs/assets/img/data-migration/new-data-migration-mapping-fields.png">
We can now add the 'Item to Atlas Item' mapping to our Data Migration Plan and save it.
<img class="screenshot" alt="Save Atlas Sync Plan" src="/docs/assets/img/data-migration/atlas-sync-plan.png">
#### Additional layer of control with pre and post process:
Migrating data frequently involves more steps in addition to one-to-one mapping. For a Data Migration Mapping that is added to a Plan, a mapping module is generated in the module specified in that plan.
In our case, an `item_to_atlas_item` module is created under the `data_migration_mapping` directory in `Integrations` (module for the 'Atlas Sync' plan).
<img class="screenshot" alt="Mapping __init__.py" src="/docs/assets/img/data-migration/mapping-init-py.png">
You can implement the `pre_process` (receives the source doc) and `post_process` (receives both source and target docs, as well as any additional arguments) methods, to extend the mapping process. Here's what some operations could look like:
<img class="screenshot" alt="Pre and Post Process" src="/docs/assets/img/data-migration/mapping-pre-and-post-process.png">
### Data Migration Connector
Now, to connect to the remote source, we need to create a *Data Migration Connector*.
<img class="screenshot" alt="New Data Migration Connector" src="/docs/assets/img/data-migration/new-connector.png">
We only have two connector types right now, let's add another Connector Type in the Data Migration Connector DocType.
<img class="screenshot" alt="Add Connector Type in Data Migration Connector" src="/docs/assets/img/data-migration/add-connector-type.png">
Now, let's create a new Data Migration Connector.
<img class="screenshot" alt="Atlas Connector" src="/docs/assets/img/data-migration/atlas-connector.png">
As you can see we chose the Connector Type as Atlas. We also added the hostname, username and password for our Atlas instance so that we can authenticate.
Now, we need to write the code for our connector so that we can actually push data.
Create a new file called `atlas_connection.py` in `frappe/data_migration/doctype/data_migration_connector/connectors/` directory. Other connectors also live here.
We just have to implement the `insert`, `update` and `delete` methods for our atlas connector. We also need to write the code to connect to our Atlas instance in the `__init__` method. Just see `frappe_connection.py` for reference.
<img class="screenshot" alt="Atlas Connection file" src="/docs/assets/img/data-migration/atlas-connection-py.png">
After creating the Atlas Connector, we also need to import it into `data_migration_connector.py`
<img class="screenshot" alt="Edit Connector file" src="/docs/assets/img/data-migration/edit-connector-py.png">
### Data Migration Run
Now that we have our connector, the last thing to do is to create a new *Data Migration Run*.
A Data Migration Run takes a Data Migration Plan and Data Migration Connector and execute the plan according to our configuration. It takes care of queueing, batching, delta updates and more.
<img class="screenshot" alt="Data Migration Run" src="/docs/assets/img/data-migration/data-migration-run.png">
Just click Run. It will now push our Items to the remote Atlas instance and you can see the progress which updates in realtime.
After a run is executed successfully, you cannot run it again. You will have to create another run and execute it.
Data Migration Run will try to be as efficient as possible, so the next time you execute it, it will only push those items which were changed or failed in the last run.
> Note: Data Migration Tool is still in beta. If you find any issues please report them [here](https://github.com/frappe/erpnext/issues)
<!-- markdown -->

View file

@ -0,0 +1,25 @@
# Email Notifications For Failed Background Jobs
<!-- markdown -->
<p>Frappé handles failure of jobs in the following way,</p><p>1) If a job fails, (raises exception), it's logged in Scheduler Log and&nbsp; <code>logs/worker.error.log</code>.<br>2) Keeps a lock file and would not run anymore if lock file is there.<br>3) Raises LockTimeoutError in case the lock file is more than 10 minutes old.</p>
<p>You can configure email notification for scheduler errors. By writing a file, <code>sites/common_site_config.json</code> with content<br></p>
<pre><code class="json hljs">{
"<span class="hljs-attribute">celery_error_emails</span>": <span class="hljs-value">{
"<span class="hljs-attribute">ADMINS</span>": <span class="hljs-value">[
[
<span class="hljs-string">"Person 1"</span>,
<span class="hljs-string">"person1@example.com"</span>
],
[
<span class="hljs-string">"Person2 "</span>,
<span class="hljs-string">"person2@example.com"</span>
]
]</span>,
"<span class="hljs-attribute">SERVER_EMAIL</span>": <span class="hljs-value"><span class="hljs-string">"exceptions@example.com"</span>
</span>}
</span>}</code></pre>
<p>One limitation is that it'll use local mailserver on port 25 to send the emails.</p>

View file

@ -0,0 +1,66 @@
# How To Enable Social Logins
Use Facebook, Google or GitHub authentication to login to Frappé, and your users will be spared from remembering another password.
The system uses the **Email Address** supplied by these services to **match with an existing user** in Frappé. If no such user is found, **a new user is created** of the default type **Website User**, if Signup is not disabled in Website Settings. Any System Manager can later change the user type from **Website User** to **System User**, so that the user can access the Desktop.
#### Login screen with Social Logins enabled
<img class="screenshot" alt="Login screen with Social Logins enabled" src="/docs/assets/img/social-logins.png">
To enable these signups, you need to have **Client ID** and **Client Secret** from these authentication services for your Frappé site. The Client ID and Client Secret are to be set in Website > Setup > Social Login Keys. Here are the steps to obtain these credentials.
> Use **https://{{ yoursite }}** if your site is HTTPS enabled.
---
### Facebook
1. Go to [https://developers.facebook.com](https://developers.facebook.com)
1. Click on Apps (topbar) > New App, fill in the form.
1. Go to Settings > Basic, set the **Contact Email** and save the changes.
1. Go to Settings > Advanced, find the field **Valid OAuth redirect URIs**, and enter:
**http://{{ yoursite }}/api/method/frappe.www.login.login\_via\_facebook**
1. Save the changes in Advance tab.
1. Go to Status & Review and switch on "Do you want to make this app and all its live features available to the general public?"
1. Go to Dashboard, click on the show button besides App Secret, and copy the App ID and App Secret into **Desktop > Website > Setup > Social Login Keys**
<div class="embed-responsive embed-responsive-16by9">
<iframe src="https://www.youtube.com/embed/zC6Q6gIfiw8" class="embed-responsive-item" allowfullscreen></iframe>
</div>
---
### Google
1. Go to [https://console.developers.google.com](https://console.developers.google.com)
1. Create a new Project and fill in the form.
1. Click on APIs & Auth > Credentials > Create new Client ID
1. Fill the form with:
- Web Application
- Authorized JavaScript origins as **http://{{ yoursite }}**
- Authorized redirect URI as
**http://{{ yoursite }}/api/method/frappe.www.login.login\_via\_google**
1. Go to the section **Client ID for web application** and copy the Client ID and Client Secret into **Desktop > Website > Setup > Social Login Keys**
<div class="embed-responsive embed-responsive-16by9">
<iframe src="https://www.youtube.com/embed/w_EAttrE9sw" class="embed-responsive-item" allowfullscreen></iframe>
</div>
---
### GitHub
1. Go to [https://github.com/settings/applications](https://github.com/settings/applications)
1. Click on **Register new application**
1. Fill the form with:
- Homepage URL as **http://{{ yoursite }}**
- Authorization callback URL as
**http://{{ yoursite }}/api/method/frappe.www.login.login\_via\_github**
1. Click on Register application.
1. Copy the generated Client ID and Client Secret into **Desktop > Website > Setup > Social Login Keys**
<div class="embed-responsive embed-responsive-16by9">
<iframe src="https://www.youtube.com/embed/bG71DxxkVjQ" class="embed-responsive-item" allowfullscreen></iframe>
</div>
<!-- markdown -->

View file

@ -0,0 +1,15 @@
# How To Migrate Doctype Changes To Production
#### 1. DocType / Schema Changes
If you are in `developer_mode`, the `.json` files for each **DocType** are automatically updated.
When you update in your production using `--latest` or `bench update`, these changes are updated in the site's schema too!
#### 2. Permissions
Permissions do not get updated because the user may have changed them. To update permissions, you can add a new patch in the `patches.txt` of your app.
execute:frappe.permissions.reset_perms("[docype]")
<!-- markdown -->

View file

@ -0,0 +1,5 @@
# Deployment
Deploying your apps on remote servers
{index}

View file

@ -0,0 +1,4 @@
migrations
how-to-migrate-doctype-changes-to-production
email-notifications-for-failed-background-jobs
how-to-enable-social-logins

View file

@ -0,0 +1,69 @@
# Migrations
A project often undergoes changes related to database schema during course of
its life. It may also be required patch existing data. Frappé bundles tools to
handle these schenarios.
When you pull updates from any Frappé app (including Frappé), you should run
`bench migrate` to apply schema changes and data migrations if any.
## Schema changes
You can edit a DocType to add, remove or change fields. On saving a DocType,
a JSON file containing the DocType data is added to source tree of your app.
When you add an app to a site, the DocTypes are installed using this JSON file.
For making schema changes, it's required to set `developer_mode` in the
configuration.
On running a sync (`bench migrate`), doctypes in the system are synced to
their latest version from the JSON files in the app.
Note: Fields are soft deleted ie. the columns are not removed from the database
table and however, they will not be visible in the documents. This is done to
avoid any potential data loss situations and to allow you write related data
migrations which might need values from deleted fields.
Note: Frappé doesn't support reverse schema migrations.
## Data Migrations
On introducing data related changes, you might want to run one off scripts to
change existing data to match expectations as per new code.
To add a data migration to your code, you will have to write an `execute`
function to a python module and add it to `patches.txt` of your app.
It is recommended to make a file with a patch number and name in its path and
add it to a patches package (directory) in your app. You can then add a line
with dotted path to the patch module to `patches.txt`.
The directory structure followed in Frappé is as below
frappe
└── patches
└── 4_0
└── my_awesome_patch.py
The patch can be added to `patches.txt` by adding a line like
frappe.patches.4_0.my_awesome_patch
The metadata ie. DocType available in the execute function will be the latest as
per JSON files in the apps. However, you will not be able to access metadata of
any previous states of the system.
#### One off Python statements
You can also add one off python statements in `patches.txt` using the syntax,
execute:{python statement}
For example,
execute:frappe.get_doc("User", "Guest").save()
Note: All lines in patches.txt have to be unique. If you want to run a line
twice, you can make it unique by adding a distinct comment.
For Example,
execute:frappe.installer.make_site_dirs() #2014-02-19

View file

@ -0,0 +1,19 @@
# Formatter For Link Fields
In case where a code and a name is maintained for an entity, (for example for Employee there may be an Employee Code and Employee Name) and we want to show both the ID and name in a link field, we can make a formatter.
#### Example:
frappe.form.link_formatters['Employee'] = function(value, doc) {
if(doc.employee_name && doc.employee_name !== value) {
return value + ': ' + doc.employee_name;
} else {
return value;
}
}
Notes:
1. Both the primary key (`name) and the descriptive name (e.g. `employee_name`) must be present in the document. The descriptive name field could be hidden
1. This needs to be loaded before the document is loaded and can be re-used for all forms. You can also add it in `build.json`

View file

@ -0,0 +1,5 @@
# Desk Customization
Articles related to customization of Frappé Desk
{index}

View file

@ -0,0 +1,3 @@
# Making Charts
[**Frappé Charts**](https://frappe.github.io/charts/) enables you to render simple line, bar or percentage graphs for single or multiple discreet sets of data points. You can also set special checkpoint values and summary stats. Check out the docs at https://frappe.github.io/charts/ to learn more.

View file

@ -0,0 +1,7 @@
# Guides
The Frappé Framework is a server side and client side framework and is built with the philosophy make it a "battries included" framework. It has libraries and API for everything from authentication to reports.
In this section we will try and cover the most commonly used API on client and server side that will be useful for app development.
{index}

View file

@ -0,0 +1,7 @@
basics
app-development
deployment
reports-and-printing
portal-development
data
integration

View file

@ -0,0 +1,74 @@
# Google GSuite
Frappe allows you to use Google's Gsuite documents as templates, generate from them a new Gsuite document that will be placed in a chosen folder. Variables can populated in both the body and the name of the Gsuite document using the standard Jinja2 format. Once generated, the Gsuite document will remain associate to the DocType as an attachment.
The Gsuite document is generated by invoking the "attach file" function of any DocType.
A common use cases of this features is populating contracts from customer/employee/supplier data.
## 1. Enable integration with Google Gsuite
### 1.1 Publish Google apps script
*If you will use the default script you can go to 1.2*
1. Go to [https://script.google.com](https://script.google.com)
1. Create a new Project. Click on **File > New > Project**
1. Copy the code of **Desk > Explore > Integrations > GSuite Settings > Google Apps Script** to clipboard and paste to an empty Code.gs in script.google.com
1. Save the Project. Click on **File > Save > Enter new project name**
1. Deploy the app. Click on **Publish > Deploy as web app**
1. Copy "Current web app URL" into **Desk > Explore > Integrations > GSuite Settings > Script URL**
1. Click on OK but don't close the script
### 1.2
### 1.2 Get Google access
1. Go to your Google project console and select your project or create a new one. [https://console.developers.google.com](https://console.developers.google.com)
1. In Library click on **Google Drive API** and **Enable**
1. Click on **Credentials > Create Credentials > OAuth Client ID**
1. Fill the form with:
- Web Application
- Authorized redirect URI as `http://{{ yoursite }}/?cmd=frappe.integrations.doctype.gsuite_settings.gsuite_settings.gsuite_callback`
1. Copy the Client ID and Client Secret into **Desk > Explore > Integrations > GSuite Settings > Client ID and Client Secret**
1. Save GSuite Settings
### 1.3 Test Script
1. Click on **Allow GSuite Access** and you will be redirected to select the user and give access. If you have any error please verify you are using the correct Authorized Redirect URI.
You can find the complete URI Gsuite redirected to in the final part of the URL of the error page. Check that the protocol `http://` or `https://` matches the one your using.
1. Click on **Run Script test**. You should be asked to give permission.
## 2. GSuite Templates
### 2.1 Google Document as Template
1. Create a new Document or use one you already have. Set variables as you need. Variables are defined with `{{VARIABLE}}` with ***VARIABLE*** is the field of your Doctype
For Example,
If this document will be used to employee and the Doctype has the field ***name*** then you can use it in Google Docs ad `{{name}}`
1. Get the ID of that Document from url of your document.
For example: in this document url `https://docs.google.com/document/d/1Y2_btbwSqPIILLcJstHnSm1u5dgYE0QJspcZBImZQso/edit` the document ID is `1Y2_btbwSqPIILLcJstHnSm1u5dgYE0QJspcZBImZQso`
1. Get the ID of the folder where you want to place the generated documents. (You can step this point if you want to place the generated documents in Google Drive root. )
For example: in this folder url `https://drive.google.com/drive/u/0/folders/0BxmFzZZUHbgyQzVJNzY5eG5jbmc` the folder ID is `0BxmFzZZUHbgyQzVJNzY5eG5jbmc`
### 2.2 Associate the Template to a Doctype
1. Go to **Desk > Explore > Integrations > GSuite Templates > New**
2. Fill the form with:
- Template Name (Example: `Employee Contract`)
- Related DocType (Example: `Employee`)
- Template ID is the Document ID you get from your Google Docs (Example: `1Y2_btbwSqPIILLcJstHnSm1u5dgYE0QJspcZBImZQso`)
- Document name is the name of the new files. You can use field from DocType (Example: `Employee Contract of {name}`)
- Destination ID is the folder ID of your files created from this Template. (Example: `0BxmFzZZUHbgyQzVJNzY5eG5jbmc`)
## 3. Create Documents
1. Go to a Document you already have a Template for (Example: Employee > John Doe)
2. Click on **Attach File**
3. On **GSuite Document** section select the Template and click **Attach**
4. You should see the generated document is already created and attached
5. Clicking on the attached document will open it inside Gsuite

View file

@ -0,0 +1,53 @@
# How to setup oauth?
<a href="https://tools.ietf.org/html/rfc6749">OAuth 2.0</a> provider based on <a href="https://github.com/idan/oauthlib">oauthlib</a> is built into frappe. Third party apps can now access resources of users based on Frappé Role and User permission system. To setup an app to access
## OAuth defines four roles
#### resource owner
An entity capable of granting access to a protected resource. When the resource owner is a person, it is referred to as an end-user.
#### resource server
The server hosting the protected resources, capable of accepting and responding to protected resource requests using access tokens.
#### client
An application making protected resource requests on behalf of the resource owner and with its authorization. The term "client" does not imply any particular implementation characteristics (e.g.,
whether the application executes on a server, a desktop, or other devices).
#### authorization server
The server issuing access tokens to the client after successfully authenticating the resource owner and obtaining authorization.
## Setup OAuth Provider
System Managers can setup behavior of confirmation message as `Force` or `Auto` in OAuth Provider Settings.
If Force is selected the system will always ask for user's confirmation. If Auto is selected system asks for the confirmation only if there are no active tokens for the user.
Go to
> Setup > Integrations > OAuth Provider Settings
<img class="screenshot" src="/docs/assets/img/oauth_provider_settings.png">
### Add Primary Server
This is the main server hosting all the users. e.g. `https://frappe.io`. To setup this as the main server, go to *Setup* > *Integrations* > *Social Login Keys* and enter `https://frappe.io` in the field `Frappé Server URL`. This URL repeats in all other Frappé servers who connect to this server to authenticate. Effectively, this is the main Identity Provider (IDP).
Under this server add as many `OAuth Client`(s) as required.
## Add a Client App
As a System Manager go to
> Setup > Integrations > OAuth Client
<img class="screenshot" src="/docs/assets/img/oauth2_client_app.png">
To add a client fill in the following details
1. **App Name** : Enter App Name e.g. CAVS
2. **Skip Authorization** : If this is checked, during authentication there won't be me any confirmation message
3. **Scopes** : List of scopes shown to user along with confirmation message. scopes are separated by semicolon ';'
4. **Redirect URIs** : List of Redirect URIs separated by semicolon ';'
5. **Default Redirect URIs** : Default Redirect URI from list of Redirect URIs
6. **Grant Type**: select `Authorization Code`
7. **Response Type**: select `Code`

View file

@ -0,0 +1,3 @@
# Integrations
{index}

View file

@ -0,0 +1,5 @@
rest_api
how_to_setup_oauth
using_oauth
openid_connect_and_frappe_social_login
google_gsuite

View file

@ -0,0 +1,72 @@
# OpenID Connect and Frappé social login
## OpenID Connect
Frappé also uses Open ID connect essential standard for authenticating users. To get `id_token` with `access_token`, pass `openid` as the value for the scope parameter during authorization request.
If the scope is `openid` the JSON response with `access_token` will also include a JSON Web Token (`id_token`) signed with `HS256` and `Client Secret`. The decoded `id_token` includes the `at_hash`.
Example Bearer Token with scope `openid`
```
{
"token_type": "Bearer",
"id_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6Imp3dCJ9.eyJpc3MiOiJodHRwczovL21udGVjaG5pcXVlLmNvbSIsImF0X2hhc2giOiJOQlFXbExJUy1lQ1BXd1d4Y0EwaVpnIiwiYXVkIjoiYjg3NzJhZWQ1YyIsImV4cCI6MTQ3Nzk1NTYzMywic3ViIjoiNWFjNDE2NThkZjFiZTE1MjI4M2QxYTk0YjhmYzcwNDIifQ.1GRvhk5wNoR4GWoeQfleEDgtLS5nvj9nsO4xd8QE-Uk",
"access_token": "ZJD04ldyyvjuAngjgBrgHwxcOig4vW",
"scope": "openid",
"expires_in": 3600,
"refresh_token": "2pBTDTGhjzs2EWRkcNV1N67yw0nizS"
}
```
## Frappé social login setup
In this example there are 2 servers,
### Primary Server
This is the main server hosting all the users. e.g. `https://frappe.io`. To setup this as the main server, go to *Setup* > *Integrations* > *Social Login Keys* and enter `https://frappe.io` in the field `Frappé Server URL`. This URL repeats in all other Frappé servers who connect to this server to authenticate. Effectively, this is the main Identity Provider (IDP).
Under this server add as many `OAuth Client`(s) as required. Because we are setting up one app server, add only one `OAuth Client`
### Frappé App Server
This is the client connecting to the IDP. Go to *Setup* > *Integrations* > *Social Login Keys* on this server and add appropriate values to `Frappé Client ID` and `Frappé Client Secret` (refer to client added in primary server). As mentioned before keep the `Frappé Server URL` as `https://frappe.io`
Now you will see Frappé icon on the login page. Click on this icon to login with account created in primary server (IDP) `https://frappe.io`
**Note**: If `Skip Authorization` is checked while registering a client, page to allow or deny the granting access to resource is not shown. This can be used if the apps are internal to one organization and seamless user experience is needed.
## Steps
### Part 1 : on Frappé Identity Provider (IDP)
Login to IDP
<img class="screenshot" src="/docs/assets/img/00-login-to-idp.png">
Add OAuth Client on IDP
<img class="screenshot" src="/docs/assets/img/01-add-oauth-client-on-idp.png">
Set Server URL on IDP
<img class="screenshot" src="/docs/assets/img/02-set-server-url-on-idp.png">
### Part 2 : on Frappé App Server
Set `Frappé Client ID` and `Frappé Client Secret` on App server (refer the client set on IDP)
<img class="screenshot" src="/docs/assets/img/03-set-clientid-client-secret-server-on-app-server.png">
**Note**: Frappé Server URL is the main server where identities from your organization are stored.
Login Screen on App Server (login with frappe)
<img class="screenshot" src="/docs/assets/img/04-login-screen-on-app-server.png">
### Part 3 : Redirected on IDP
login with user on IDP
<img class="screenshot" src="/docs/assets/img/05-login-with-user-on-idp.png">
Confirm Access on IDP
<img class="screenshot" src="/docs/assets/img/06-confirm-grant-access-on-idp.png">
### Part 4 : Back on App Server
Logged in on app server with ID from IDP
<img class="screenshot" src="/docs/assets/img/07-logged-in-as-website-user-with-id-from-idp.png">

View file

@ -0,0 +1,285 @@
# REST API
Frappé ships with an HTTP API. There are two parts of this API.
1. Remote Procedure Calls (RPC)
2. REST
## 1. RPC
A request to an endpoint `/api/method/{dotted.path.to.function}` will call
a whitelisted python function. A function can be whitelisted using the
`frappe.whitelist` decorator.
For example, Add the following to sample\_app/\_\_init\_\_.py
@frappe.whitelist(allow_guest=True)
def ping():
return 'pong'
<span class="label label-success">GET</span> http://frappe.local:8000**/api/method/sample_app.ping**
_Response:_
{
"message": "pong"
}
## 2. REST
All documents in Frappé are available via a RESTful API with prefix
`/api/resource/`.
### Login
To login, you will have to send a POST request to the login method.
<span class="label label-info">POST</span> http://frappe.local:8000**/api/method/login**
usr=Administrator&pwd=admin
_Response:_
{
"full_name": "Administrator",
"message": "Logged In"
}
Try to make an authenticated request
<span class="label label-success">GET</span> http://frappe.local:8000**/api/method/frappe.auth.get\_logged\_user**
_Response:_
{
"message": "Administrator"
}
### Listing Documents
To list documents, the URL endpoint is `/api/resource/{doctype}` and the
expected HTTP verb is GET.
Response is returned as JSON Object and the listing is an array in with the key `data`.
<span class="label label-success">GET</span> http://frappe.local:8000**/api/resource/Person**
_Response:_
{
"data": [
{
"name": "000000012"
},
{
"name": "000000008"
}
]
}
#### Fields
By default, only name field is included in the listing, to add more fields, you
can pass the fields param to GET request. The param has to be a JSON array.
<span class="label label-success">GET</span> http://frappe.local:8000**/api/resource/Person/?fields=["name", "first\_name"]**
_Response:_
{
"data": [
{
"first_name": "Jane",
"name": "000000012"
},
{
"first_name": "John",
"name": "000000008"
}
]
}
#### Filters
You can filter the listing using sql conditions by passing them as the `filters`
GET param. Each condition is an array of the format, [{doctype}, {field},
{operator}, {operand}].
Eg, to filter persons with name Jane, pass a param `filters=[["Person", "first_name", "=", "Jane"]]`
<span class="label label-success">GET</span> http://frappe.local:8000**/api/resource/Person/**
_Response:_
{
"data": [
{
"name": "000000012"
}
]
}
#### Pagination
All listings are returned paginated by 20 items. To change the page size, you
can pass `limit_page_length`. To request succesive pages, pass `limit_start` as
per your `limit_page_length`.
For Example, to request second page, pass `limit_start` as 20.
<span class="label label-success">GET</span> http://frappe.local:8000**/api/resource/DocType**
_Response:_
{
"data": [
{
"name": "testdoc"
},
{
"name": "Person"
},
......
{
"name": "Website Template"
}
]
}
<span class="label label-success">GET</span> http://frappe.local:8000**/api/resource/DocType?limit_start=20**
_Response:_
{
"data": [
{
"name": "Website Route"
},
{
"name": "Version"
},
{
"name": "Blog Post"
},
......
{
"name": "Custom Field"
}
]
}
### CRUD
#### Create
You can create a document by sending a `POST` request to the url, `/api/resource/{doctype}`.
<span class="label label-info">POST</span> http://frappe.local:8000**/api/resource/Person**
_Body_:
data={"first_name": "Robert"}
_Response:_
{
"data": {
"first_name": "Robert",
"last_name": null,
"modified_by": "Administrator",
"name": "000000051",
"parent": null,
"creation": "2014-05-04 17:22:38.037685",
"modified": "2014-05-04 17:22:38.037685",
"doctype": "Person",
"idx": null,
"parenttype": null,
"owner": "Administrator",
"docstatus": 0,
"parentfield": null
}
}
Note: `POST` requests are to be sent along with `X-Frappe-CSRF-Token:<csrf-token>` header.
#### Read
You can get a document by its name using the url, `/api/resource/{doctype}/{name}`
For Example,
<span class="label label-success">GET</span> http://frappe.local:8000**/api/resource/Person/000000012**
_Response:_
{
"data": {
"first_name": "Jane",
"last_name": "Doe",
"modified_by": "Administrator",
"name": "000000012",
"parent": null,
"creation": "2014-04-25 17:56:51.105372",
"modified": "2014-04-25 17:56:51.105372",
"doctype": "Person",
"idx": null,
"parenttype": null,
"owner": "Administrator",
"docstatus": 0,
"parentfield": null
}
}
### Update
You can create a document by sending a `PUT` request to the url,
`/api/resource/{doctype}`. This acts like a `PATCH` HTTP request in which you do
not have to send the whole document but only the parts you want to change.
For Example,
<span class="label label-primary">PUT</span> http://frappe.local:8000**/api/resource/Person/000000008**
_Body:_
data={"last_name": "Watson"}
_Response:_
{
"data": {
"first_name": "John ",
"last_name": "Watson",
"modified_by": "Administrator",
"name": "000000008",
"creation": "2014-04-25 17:26:22.728327",
"modified": "2014-05-04 18:21:45.385995",
"doctype": "Person",
"owner": "Administrator",
"docstatus": 0
}
}
### Delete
You can delete a document by its name by sending a `DELETE` request to the url,
`/api/resource/{doctype}/{name}`.
For Example,
<span class="label label-danger">DELETE</span> http://frappe.local:8000**/api/resource/Person/000000008**
_Response:_
{"message":"ok"}

View file

@ -0,0 +1,109 @@
# Using OAuth
Once the client and provider settings are entered, following steps can be used to start using OAuth 2.0
### Authorization Code Endpoint
#### Authorization Request
URL:
```
[GET] 0.0.0.0:8000/api/method/frappe.integrations.oauth2.authorize
```
Params:
```
client_id = <client ID of registered app>
scope = <access scope, e.g. scope=project will allow you to access project doctypes.>
response_type = "code"
redirect_uri = <redirect uri from OAuth Client>
```
#### Confirmation Dialog
<img class="screenshot" src="/docs/assets/img/oauth_confirmation_page.png">
Click 'Allow' to receive authorization code in redirect uri.
```
http://localhost:3000/oauth_code?code=plkj2mqDLwaLJAgDBAkyR1W8Co08Ud
```
If user clicks 'Deny' receive error
```
http://localhost:3000/oauth_code?error=access_denied
```
### Token Endpoints
#### Get Access Token
URL:
```
[POST] 0.0.0.0:8000/api/method/frappe.integrations.oauth2.get_token
```
Params:
```
grant_type = "authorization_code"
code = <code received in redirect uri after confirmation>
redirect_uri = <valid redirect uri>
client_id = <client ID of app from OAuth Client>
```
Response:
```
{
"access_token": "pNO2DpTMHTcFHYUXwzs74k6idQBmnI",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "cp74cxbbDgaxFuUZ8Usc7egYlhKbH1",
"scope": "project"
}
```
#### Refresh Access Token
URL:
```
[POST] 0.0.0.0:8000/api/method/frappe.integrations.oauth2.get_token
```
Params:
```
grant_type = "refresh_token"
refresh_token = <refresh token from the response of get_token call with grant_type=authorization_code>
redirect_uri = <valid redirect uri>
client_id = <client ID of app from OAuth Client>
```
Response:
```
{
"access_token": "Ywz1iNk0b21iAmjWAYnFWT4CuudHD5",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "PNux3Q8Citr3s9rl2zEsKuU1l8bSN5",
"scope": "project"
}
```
#### Revoke Token Endpoint
URL:
```
[POST] 0.0.0.0:8000/api/method/frappe.integrations.oauth2.revoke_token
```
Params:
```
token = <access token to be revoked>
```
Success Response
```
status : 200
{"message": "success"}
```
Error Response:
```
status : 400
{"message": "bad request"}
```
### Accessing Resource
Add header `Authorizaton: Bearer <valid_bearer_token>` to Frappé's REST API endpoints to access user's resource

View file

@ -0,0 +1,103 @@
# Webhooks
Webhooks are "user-defined HTTP callbacks". You can create webhook which triggers on Doc Event of the selected DocType. When the `doc_events` occurs, the source site makes an HTTP request to the URI configured for the webhook. Users can configure them to cause events on one site to invoke behaviour on another.
#### Configure Webhook
To add Webhook go to
> Integrations > External Documents > Webhook
Webhook
<img class="screenshot" src="/docs/assets/img/webhook.png">
1. Select the DocType for which hook needs to be triggered e.g. Note
2. Select the DocEvent for which hook needs to be triggered e.g. on_trash
3. Enter a valid request URL. On occurence of DocEvent, POST request with doc's json as data is made to the URL.
4. Optionally you can add headers to the request to be made. Useful for sending api key if required.
5. Optionally you can select fields and set its `key` to be sent as data json
e.g. Webhook
- **DocType** : `Quotation`
- **Doc Event** : `on_update`
- **Request URL** : `https://httpbin.org/post`
- **Webhook Data** :
1. **Fieldname** : `name` and **Key** : `id`
2. **Fieldname** : `items` and **Key** : `lineItems`
Note: if no headers or data is present, request will be made without any header or body
Example response of request sent by frappe server on `Quotation` - `on_update` to https://httpbin.org/post:
```
{
"args": {},
"data": "{\"lineItems\": [{\"stock_qty\": 1.0, \"base_price_list_rate\": 1.0, \"image\": \"\", \"creation\": \"2017-09-14 13:41:58.373023\", \"base_amount\": 1.0, \"qty\": 1.0, \"margin_rate_or_amount\": 0.0, \"rate\": 1.0, \"owner\": \"Administrator\", \"stock_uom\": \"Unit\", \"base_net_amount\": 1.0, \"page_break\": 0, \"modified_by\": \"Administrator\", \"base_net_rate\": 1.0, \"discount_percentage\": 0.0, \"item_name\": \"I1\", \"amount\": 1.0, \"actual_qty\": 0.0, \"net_rate\": 1.0, \"conversion_factor\": 1.0, \"warehouse\": \"Finished Goods - R\", \"docstatus\": 0, \"prevdoc_docname\": null, \"uom\": \"Unit\", \"description\": \"I1\", \"parent\": \"QTN-00001\", \"brand\": null, \"gst_hsn_code\": null, \"base_rate\": 1.0, \"item_code\": \"I1\", \"projected_qty\": 0.0, \"margin_type\": \"\", \"doctype\": \"Quotation Item\", \"rate_with_margin\": 0.0, \"pricing_rule\": null, \"price_list_rate\": 1.0, \"name\": \"QUOD/00001\", \"idx\": 1, \"item_tax_rate\": \"{}\", \"item_group\": \"Products\", \"modified\": \"2017-09-14 17:09:51.239271\", \"parenttype\": \"Quotation\", \"customer_item_code\": null, \"net_amount\": 1.0, \"prevdoc_doctype\": null, \"parentfield\": \"items\"}], \"id\": \"QTN-00001\"}",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Connection": "close",
"Content-Length": "1075",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.18.1"
},
"json": {
"id": "QTN-00001",
"lineItems": [
{
"actual_qty": 0.0,
"amount": 1.0,
"base_amount": 1.0,
"base_net_amount": 1.0,
"base_net_rate": 1.0,
"base_price_list_rate": 1.0,
"base_rate": 1.0,
"brand": null,
"conversion_factor": 1.0,
"creation": "2017-09-14 13:41:58.373023",
"customer_item_code": null,
"description": "I1",
"discount_percentage": 0.0,
"docstatus": 0,
"doctype": "Quotation Item",
"gst_hsn_code": null,
"idx": 1,
"image": "",
"item_code": "I1",
"item_group": "Products",
"item_name": "I1",
"item_tax_rate": "{}",
"margin_rate_or_amount": 0.0,
"margin_type": "",
"modified": "2017-09-14 17:09:51.239271",
"modified_by": "Administrator",
"name": "QUOD/00001",
"net_amount": 1.0,
"net_rate": 1.0,
"owner": "Administrator",
"page_break": 0,
"parent": "QTN-00001",
"parentfield": "items",
"parenttype": "Quotation",
"prevdoc_docname": null,
"prevdoc_doctype": null,
"price_list_rate": 1.0,
"pricing_rule": null,
"projected_qty": 0.0,
"qty": 1.0,
"rate": 1.0,
"rate_with_margin": 0.0,
"stock_qty": 1.0,
"stock_uom": "Unit",
"uom": "Unit",
"warehouse": "Finished Goods - R"
}
]
},
"url": "https://httpbin.org/post"
}
```

View file

@ -0,0 +1,5 @@
# Pages
You can make your website by adding pages to the `/www` folder of your website. The urls of your site will match the path of your pages within the `/www` folder.
Pages must be `.html` or `.md` (Markdown) files. Basic HTML template is provided in frappe in `frappe/templates/base_template.html`

View file

@ -0,0 +1,39 @@
# Adding Pages
To add pages, just add `.html` or `.md` files in the `www` folder. The pages must only have the content, not the `<html>` and `<body>` tags.
You can also write markdown pages
### Index
The first file in a folder must be called `index.md` or `index.html`
Either file must be present for the system to make this a valid folder to build pages.
### Markdown
# This is a title
This is some page content
a [link](/link/to/page)
### Adding Links
Links urls to pages can be given without the `.html` extension for example `/home/link`
### Title
The first `<h1>` block if present will be the page title if not specified in a special tag. If no `<h1>` or title is specified, the file name will be the title.
### Adding CSS
You can also add a `.css` file with the same filename (e.g. `index.css` for `index.md`) that will be rendered with the page.
### Special Tags
1. `<!-- jinja -->` will make the page render in Jinja
2. `<!-- title: Adding Pages -->` will add a custom title
3. `<!-- no-breadcrumbs -->` will not add breadcrumbs in the page
4. `<!-- static -->` will enable caching (if you have used Jinja templating)
{next}

View file

@ -0,0 +1,29 @@
# Table of Contents
You can add a table of contents by adding `{index}` string on a new line.
You can also make Previous and Next buttons by adding `previous` or `next` in `{}`
### Showing contents
# This is a title
Hello paragraph
### Contents:
{index}
{next}
### Ordering
You can defining the ordering of pages in index by defining the index.txt file in your folder. The index.txt file must have the names (without extensions) of the pages in that folder indicating the order.
For example for this folder the `index.txt` looks like:
adding-pages
ordering
contents
context
building

View file

@ -0,0 +1,26 @@
# Dynamic Pages
You can render pages dynamically using Jinja templating language. To query data, you can update that `context` object that you pass to the template.
This can be done by adding a `.py` file with the same filename (e.g. `index.py` for `index.md`) with a `get_context` method.
### Example
If you want to show a page to see users, make a `users.html` and `users.py` file in the `www/` folder.
In `users.py`:
import frappe
def get_context(context):
context.users = frappe.db.sql("select first_name, last_name from `tabUser`")
In `users.html`:
<h3>List of Users</h3>
<ol>
{% for user in users %}
<li>{{ user.first_name }} {{ user.last_name or "" }}</li>
{% endfor %}
</ol>
{next}

View file

@ -0,0 +1,89 @@
# Generators
If every document in a table (DocType) corresponds to a web-page, you can setup generators.
To setup a generator you must:
1. Add a field `route` that specifies the route of the page
2. Add a condition field to indicate whether a page is viewable or not.
3. Add the doctype name in `website_generators` in `hooks.py` of your app.
4. Subclass the controller from `frappe.website.website_generator.WebsiteGenerator`
5. Create a template for your page
6. Add custom properties (context) for the template
6. Customize route and list view
Let us see this with the help of an example:
## Example
#### 1. Add fields
We added `published`, `route` in the DocType
**Note:** The field `route` is mandatory
<img class="screenshot" alt="Generator fields" src="/docs/assets/img/generators.png">
#### 2. Added Website Generator to Hooks
Since Job Opening is in `erpnext`, we have added to the list of existing generator hooks:
website_generators = ["Item Group", "Item", "Sales Partner", "Job Opening"]
If the `website_generators` property does not exist in your hooks.py, add it!
#### 3. Controller
We add the `website` property to the **JobOpening** class in `job_opening.py`
In `get_context`, `parents` property will indicate the breadcrumbs
from frappe.website.website_generator import WebsiteGenerator
from frappe import _
# subclass from WebsiteGenerator, not Document
class JobOpening(WebsiteGenerator):
website = frappe._dict(
template = "templates/generators/job_opening.html",
condition_field = "published",
page_title_field = "job_title",
)
def get_context(self, context):
# show breadcrumbs
context.parents = [{'name': 'jobs', 'title': _('All Jobs') }]
**Note:** Once you do this, you should see the "See in Website" link on the document form.
#### 4. Add the template
Add the template in `erpnext/templates/generators/job_opening.html`
{% raw %}{% extends "templates/web.html" %}
{% block breadcrumbs %}
{% include "templates/includes/breadcrumbs.html" %}
{% endblock %}
{% block header %}
<h1>{{ job_title }}</h1>
{% endblock %}
{% block page_content %}
<div>{{ description }}</div>
<a class='btn btn-primary'
href='/job_application?job_title={{ doc.job_title }}'>
{{ _("Apply Now") }}</a>
{% endblock %}{% endraw %}
#### 5. Customizing List View
If you add a method `get_list_view` in the controller file (job_opening.py), you can set properties for the listview
def get_list_context(context):
context.title = _("Jobs")
context.introduction = _('Current Job Openings')
{next}

View file

@ -0,0 +1,17 @@
# Making Portals
Frappé has powerful tools to build portals where pages can be dynamically generated using templates (Jinja) and users can be shown records after login
#### Adding Pages
You can make your website by adding pages to the `/www` folder of your website. The urls of your site will match the path of your pages within the `/www` folder.
Pages must be `.html` or `.md` (Markdown) files. Basic HTML template is provided in frappe in `frappe/templates/base_template.html`
#### Views after Login
After logging in, the user sees a "My Account" page `/me` where user can access certain documents that are shown via a menu
The user can view records based on permissions and also add / edit them with **Web Forms**
{index}

View file

@ -0,0 +1,7 @@
adding-pages
context
generators
contents
web-forms
ordering
portal-roles

View file

@ -0,0 +1,13 @@
# Ordering
You can defining the ordering of pages in index by defining the index.txt file in your folder. The index.txt file must have the names (without extensions) of the pages in that folder indicating the order.
For example for this folder the `index.txt` looks like:
adding-pages
ordering
contents
context
building
{next}

View file

@ -0,0 +1,25 @@
# Portal Roles
Version: 7.1+
Roles can be assigned to Website Users and they will see menu based on their role
1. A default role can be set in **Portal Settings**
1. Each Portal Menu Item can have a role associated with it. If that role is set, then only those users having that role can see that menu item
1. Rules can be set for default roles that will be set on default users on hooks
<img class="screenshot" alt="Portal Settings" src="/docs/assets/img/portals/portal-settings.png">
#### Rules for Default Role
For example if the Email Address matches with a contact id, then set role Customer or Supplier:
default_roles = [
{'role': 'Customer', 'doctype':'Contact', 'email_field': 'email_id',
'filters': {'ifnull(customer, "")': ('!=', '')}},
{'role': 'Supplier', 'doctype':'Contact', 'email_field': 'email_id',
'filters': {'ifnull(supplier, "")': ('!=', '')}},
{'role': 'Student', 'doctype':'Student', 'email_field': 'student_email_id'}
]

View file

@ -0,0 +1,11 @@
# Customizing Web Forms
Web Forms are a powerful way to add forms to your website. Web forms are powerful and scriptable and from Version 7.1+ they also include tables, paging and other utilities
<img class="screenshot" alt="Web Form" src="/docs/assets/img/portals/sample-web-form.png">
### Standard Web Forms
If you check on the "Is Standard" checkbox, a new folder will be created in the `module` of the Web Form for that web form. In this folder, you will see a `.py` and `.js` file that you can customize the web form with.
{next}

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