将文档中的字段值提取到事务中

假设在“供应商”中有一个自定义字段“GSTIN”,需要在“采购订单”中获取该字段。

场景一:您希望保持此字段自动更新

在此场景中,当您保存采购订单时,该自定义字段将根据供应商中的值自动更新,并且每次保存采购订单时都会重新更新。由于此字段需要自动更新,因此它会覆盖用户输入。如果您希望允许用户输入,请参考场景二。

步骤:

  1. 为供应商文档创建一个自定义字段 GSTIN,将字段类型设置为数据。

  2. 为采购订单文档创建另一个自定义字段 GSTIN,但在此情况下,将字段类型设置为只读或勾选只读复选框。将从以下位置获取设置为 supplier.gstin。

  1. 转到用户菜单并点击“重新加载”。

  2. 现在,在新的采购订单中选择供应商后,GSTIN 将自动从所选供应商中获取。

场景二:如果未找到值,您希望允许用户输入

在此场景中,首次创建采购订单时会从供应商处获取该值。如果在供应商中未找到该值,您可以手动输入。仅当该字段为空时,才会在保存采购订单时获取该值。

步骤:

  1. 为供应商文档创建一个自定义字段 GSTIN,将字段类型设置为数据。

  1. 为采购订单文档创建另一个自定义字段 GSTIN,将字段类型设置为数据。将从以下位置获取设置为 supplier.vat_number,并勾选标题为为空时获取的复选框。

  1. 转到用户菜单并点击“重新加载”。

  2. 现在,在新的采购订单中选择供应商后,GSTIN 将自动从所选供应商中获取。如果在供应商中未找到 GSTIN,您可以手动输入。

如何在Frappe中启用开发者模式

当您处于应用设计模式,并希望您对 DocTypes、Reports 等的更改影响到应用仓库时,您必须处于开发者模式。

要启用开发者模式,请更新站点文件夹中您站点的 site_config.json 文件:

frappe-bench/sites/{your site}/site_config.json

在 frappe-bench 文件夹内,运行

bench set-config -g developer_mode 1

或者,手动在 site_config.json 的 { .. } 中写入 "developer_mode": 1

{
  "developer_mode": 1,
  ...
}

设置开发者模式后,清除缓存:

bench --site {your site} clear-cache

某些应用在开发模式下可能需要额外的依赖项才能进行构建和测试。使用 bench 安装它们:

bench setup requirements --dev

在JavaScript中使用HTML模板

在构建 JavaScript 界面时,通常需要将 DOM 渲染为 HTML 模板。Frappe 框架使用 John Resig 的 Microtemplate 脚本在 Desk 应用程序中渲染 HTML 模板。

> 注意 1:在 Frappe 中,我们使用类似 Jinja 的 {% raw %}{%{% endraw %} 标签来嵌入代码,而不是标准的 <%

> 注意 2:切勿在 HTML 模板中使用单引号 '。

要渲染模板,请执行以下操作:

  1. 在您的应用中创建一个模板 html 文件。例如:address_list.html
  2. 将其添加到您应用的 build.json 中(您可以将其包含在 frappe.min.js 或您自己的 JavaScript 文件中)。
  3. 要在您的应用中渲染它,请使用 frappe.render(frappe.templates.address_list, {[context]})

示例模板:

来自 erpnext/public/js/templates/address_list.js

<p>
    <button class="btn btn-sm btn-default btn-address">New Address</button>
</p>

{% for(var i=0, l=addrlist.length; i<l; i++) { %}
    <hr>
    {%= _("Edit") %}
    {%= addr_list[i].address_type %}

    {% if(addr_list[i].is_primary_address) { %}
        {%= __("Primary") %}
    {% } %}

    {% if(addr_list[i].is_shipping_address) { %}
        {%= __("Shipping") %}
    {% } %}

    {%= addr_list[i].display %}
{% } %}

{% if(!addrlist.length) { %}
    {%= _("No address added yet.") %}
{% } %}

如何在应用安装期间创建自定义字段

您的自定义应用在安装到新站点时,可以自动向应用之外的 DocTypes 添加自定义字段。

要实现此操作,请使用 Frappe Web 应用程序添加您的应用所需的新自定义字段。

在您的 hooks.py 文件中,添加 "Custom Fields"

fixtures = [“Custom Field”]

在提交应用之前,请使用以下命令导出 fixtures:

$ bench –site mysite export-fixtures

这将在您的应用文件夹中创建一个名为 fixtures 的新文件夹,并生成一个 .csv 或 .json 文件,其中包含您添加的自定义字段的定义。

当应用安装到新站点或通过 bench update 更新时,此文件将被自动导入。

注意:您也可以将单个 DocTypes(如“网站设置”)添加为 fixtures。

通过API插入文档

您可以通过脚本使用 frappe.get_doc 方法插入文档

示例:

1. 插入一个待办事项

todo = frappe.get_doc({“doctype”:”ToDo”, “description”: “test”})
todo.insert()


2. 不检查用户权限直接插入:

todo = frappe.get_doc({“doctype”:”ToDo”, “description”: “test”})
todo.insert(ignore_permissions = True)


3. 插入后提交

todo = frappe.get_doc({“doctype”:”ToDo”, “description”: “test”})
todo.insert(ignore_permissions=True)
todo.submit()


4. 在保存另一个文档时插入文档

class MyType(Document):
def on_update(self):
todo = frappe.get_doc({“doctype”:”ToDo”, “description”: “test”})
todo.insert()


5. 插入带有子表的文档:

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()

自定义模块图标

如果你想为你的模块创建一个自定义图标,你需要为你的模块创建一个 SVG 文件,并在你的应用的 desktop/config.py 中设置该文件的路径。

该图标首次通过 AJAX 加载,之后会被渲染显示。

示例:

from frappe import _

 def get_data():
    return {
     "Frappe Apps": {
     "color": "orange",
     "icon": "assets/frappe/images/frappe.svg",
     "label": _("Frappe.io Portal"),
     "type": "module"
    }
  }

附注:一个购买低价 SVG 图标的好地方是优秀的 Noun Project 网站。

创建新的连接应用

您可以使用已连接应用,让您的 frappe 实例代表您访问其他网络服务。

此示例演示如何连接 Google Mail,但您可以使用任何支持 OAuth 2.0 的服务。

首先,创建一个新的已连接应用并为其命名。大多数服务提供商都会提供一个类似 /.well-known/openid-configuration 的 URL,用于发布所有 OAuth 端点。输入此 URL 并点击“获取 OpenID 配置”。

如果您不知道此 URL,请前往“端点”部分,手动输入授权 URI 和令牌 URI。授权 URI 是您将被重定向以授予同意的地方。令牌 URI 是系统可以用授权码换取访问令牌或刷新令牌的端点。请查阅特定集成或第三方服务的文档以获取正确的值。

现在,配置应该如下所示:

点击“保存”。现在您应该会看到一个名为重定向 URI 的新只读字段。请注意,其最后一部分特定于您的已连接应用。您需要向您的服务提供商(在此示例中为 Google)注册此重定向 URI。

复制重定向 URI,前往第三方服务并创建新的 OAuth 2.0 客户端凭据。您需要指定授权的 JavaScript 来源以及您刚刚复制的重定向 URI。将您的 frappe 实例的域名输入为授权的 JavaScript 来源。在生产服务器上,这可能是 https://erp.mycompany.com。对于本地开发,您可以使用类似 http://localhost:8000 的内容。

授权的 JavaScript 来源和授权的重定向 URI 必须正确,这一点至关重要。否则,您将无法连接。

将第三方服务的客户端 ID 和密钥复制到您的已连接应用并保存。

接下来,您需要定义您的系统需要访问的范围(scopes)。在此示例中,我们要连接到一个电子邮件收件箱,因此我们需要范围 https://mail.google.com/。对于其他用例,请查阅您的服务提供商文档中可用的范围。

最后,我们可以定义额外的参数。Google 文档说明我们需要参数 access_type=offline,以便我们的服务器可以在后台访问电子邮件收件箱,并在无需用户干预的情况下持续刷新访问令牌。如果我们不传递此参数,临时访问权限将很快过期,我们将需要反复登录。

现在,您和您的用户可以点击右上角的“连接到…”。这将打开一个新的浏览器选项卡,显示第三方服务的同意屏幕。之后,您将被重定向回您的 frappe 实例。

在后端,您的服务器将把重定向期间获得的代码兑换为临时访问令牌,并可选地兑换为刷新令牌。然后,这些令牌可用于代表您访问第三方服务。

用自定义脚本覆盖链接查询

您可以通过工作台的客户端脚本 DocType 使用 set_query 来覆盖标准链接查询。

set_query 方法支持两种格式:set_query(field_name, options_function()) 用于常规字段,或 set_query(field_name, child_table_name, options_function()) 用于子表中的字段。

1. 添加筛选条件

您可以为查询添加筛选条件:

frappe.ui.form.on("Bank Reconciliation", "onload", function(frm) {
 frm.set_query("bank_account", function() {
 return {
 "filters": {
 "account_type": "Bank",
 "group_or_ledger": "Ledger"
 }
 };
 });
});

更复杂的查询示例:

frappe.ui.form.on("Bank Reconciliation", "onload", function(frm){
 frm.set_query("bank_account", function(){
 return {
 "filters": [
 ["Bank Account", "account_type", "=", "Bank"],
 ["Bank Account", "group_or_ledger", "!=", "Group"]
 ]
 }
 });
});

2. 调用其他方法生成结果

您还可以设置在查询时调用的服务器端方法:

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"}
 };
});

自定义方法

自定义方法应返回一个用于自动选择的项目列表。如果您想发送额外的数据,可以在列表中发送多个列。

自定义方法的参数如下:

def custom_query(doctype, txt, searchfield, start, page_len, filters)

示例:

# searches for leads which are not converted
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def lead_query(doctype, txt, searchfield, start, page_len, filters):
 return frappe.db.sql("""
 SELECT name, lead_name, company_name
 FROM `tabLead`
 WHERE docstatus < 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': "%{}%".format(txt),
 '_txt': txt.replace("%", ""),
 'start': start,
 'page_len': page_len
 })

注意: @frappe.whitelist() 用于向客户端暴露 lead_query
并且 @frappe.validate_and_sanitize_search_inputs 装饰器用于验证和清理通过 API 或客户端请求发送的用户输入,以避免潜在的 SQL 注入。

更多示例请参见:

https://github.com/frappe/erpnext/blob/develop/erpnext/controllers/queries.py

对话框类型

Frappe 提供了一组标准对话框,在编码时非常有用。

警告对话框

警告对话框用于显示非阻塞性的消息。

它有两个参数:

  • txt: 要在 Alert Dialog 中显示的消息
  • seconds: 消息显示的持续时间。默认值为 3 seconds。

示例

show_alert(‘你好,你有新消息’, 5);


提示对话框

提示对话框用于从用户那里收集数据。

它有 4 个参数:

  • fields: 包含字段对象的列表
  • callback: 用于处理对话框中数据的函数
  • title: 对话框的标题
  • primary_label: 主按钮的标签

示例

frappe.prompt([
{‘fieldname’: ‘birth’, ‘fieldtype’: ‘Date’, ‘label’: ‘出生日期’, ‘reqd’: 1}
],
function(values){
show_alert(values, 5);
},
‘年龄验证’,
‘订阅我’
)


确认对话框

确认对话框用于在执行操作前获取用户的确认。

它有 3 个参数:

  • mesage: 要在对话框中显示的消息
  • onyes: 肯定确认时的回调函数
  • oncancel: 否定确认时的回调函数

示例

frappe.confirm(
‘确定要离开此页面吗?’,
function(){
window.close();
},
function(){
show_alert(‘感谢您继续在此操作!’)
}
)


消息打印

消息打印用于向用户显示信息。

它有 2 个参数:

  • message: 要显示的消息。可以是 HTML 字符串
  • title: 对话框的标题

示例

msgprint(“服务器状态“

  • “

“

  • “”
  • “* 28% 内存
    “
  • “* 12% 处理器
    “
  • “* 0.3% 磁盘
    “
  • “
    “, ‘服务器信息’)

自定义对话框

您可以使用 frappe.ui.Dialog 扩展并构建自己的自定义对话框

示例

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(‘你好,世界’);
d.show();

删除网格行时触发事件

当子表中的某一行被删除时(即用户点击 delete 按钮),若要触发相应事件,您需要在子表中为 fieldname_remove 事件添加一个处理程序,其中 fieldname 是子表在父表声明中的字段名。

例如:

假设您的父 DocType 名为 Item,它有一个表字段链接到 Item Color DocType,声明名称为 color。

要“捕获”删除事件,请执行以下操作:

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
 }
);

同样的方法也用于触发添加事件(当用户点击 add row 按钮时):

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) {}
});

请注意,处理程序是在子 DocType 表 form.ui.on 上进行的,而不是在父 DocType 上,因此一个最小化的完整示例如下:

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
 }
);

处理程序包括:

  1. fieldname_add(添加)
  2. fieldname_move(移动)
  3. fieldname_before_remove(移除前)
  4. fieldname_remove(移除)