对话框API

Frappe 提供了一组标准、交互式且灵活的对话框,易于配置和使用。此外,还有一个更全面的 Javascript API。

frappe.msgprint

frappe.msgprint(msg, title, raise_exception, as_table, as_list, indicator, primary_action, is_minimizable, wide, realtime)

此方法仅在请求/响应周期内有效。它会向发起请求并登录 Desk 的用户显示一条消息。

参数列表包括:

  • msg:要显示的消息
  • title:模态框的标题
  • as_table:如果 msg 是列表的列表,则渲染为 HTML 表格
  • as_list:如果 msg 是列表,则渲染为 HTML 无序列表
  • primary_action:绑定一个主要的服务器端/客户端操作。
  • raise_exception:异常
  • is_wide:显示一个宽模态框
  • is_minimizable:允许用户最小化模态框
  • realtime:使用 websocket 立即发布,而不是添加到响应消息日志中
frappe.msgprint(
    msg='This file does not exist',
    title='Error',
    raise_exception=FileNotFoundError
)

frappe.msgprint

primary_action 可以包含一个 server_action client_side 操作,该操作必须包含指向相应方法的点分路径。JavaScript 函数必须是全局可用的函数。您还可以传递 hide_on_success 以在操作成功完成后关闭消息。

# msgprint with server and client side action
frappe.msgprint(msg='This file does not exist',
    title='Error',
    raise_exception=FileNotFoundError
    primary_action={
        'label': _('Perform Action'),
        'server_action': 'dotted.path.to.server.method',
        'client_action': 'dotted.path.to.client.method',
        'hide_on_success': True,
        'args': args
    }
)

带主要操作的 frappe.msgprint

frappe.throw

frappe.throw(msg, exc, title, is_minimizable, wide, as_list, primary_action)

此方法将引发异常并在 Desk 中显示消息。它本质上是 frappe.msgprint 的封装。

exc 可以传递一个可选的异常。默认情况下,它将引发一个 ValidationError 异常。

frappe.throw(
    title='Error',
    msg='This file does not exist',
    exc=FileNotFoundError
)

frappe.throw

全文搜索API

用于 Whoosh 的 Frappe 封装器

update_index_by_name(self, doc_name)

封装 update_index 方法,根据名称获取文档并更新索引。此函数会更改当前用户,应仅以管理员身份运行或在后台任务中运行。

参数:

  • self (对象):全文搜索实例
  • doc_name (字符串):要更新的文档名称

remove_document_from_index(self, doc_name)

从搜索索引中移除文档

参数:

  • self (对象):全文搜索实例
  • doc_name (字符串):要移除的文档名称

update_index(self, document)

更新文档的搜索索引

参数:

  • self (对象):全文搜索实例
  • document (字典):包含标题、路径和内容的字典

build_index(self)

为所有已解析的文档构建索引

search(self, text, scope=None, limit=20)

从当前索引中进行搜索

参数:

  • text (字符串):要搜索的文本
  • scope (字符串,可选):限制搜索范围。默认为 None。
  • limit (整数,可选):限制搜索结果数量。默认为 20。

返回:

  • [列表(字典)]:搜索结果

REST API

Frappe 框架会自动为您的所有 DocType 生成 REST API。您还可以使用它们的点分模块路径来运行任意的 Python 方法。

身份验证

有两种通过 Frappe REST API 进行身份验证的方式:基于令牌的身份验证和基于密码的身份验证。

1. 基于令牌的身份验证

令牌由 API 密钥(API Key)和 API 机密(API Secret)组成。要生成这些令牌,请按照以下步骤操作:

  1. 前往用户列表并打开一个用户。
  2. 点击“设置”选项卡。(如果您看不到选项卡,请跳过此步骤)
  3. 展开“API 访问”部分,然后点击“生成密钥”。
  4. 您将看到一个包含 API 机密的弹出窗口。复制此值并将其保存在安全的地方(例如密码管理器)。
  5. 您还会在此部分看到另一个字段“API 密钥”。

令牌是通过使用冒号 : 连接 api_keyapi_secret 生成的。将字符串 token api_key:api_secret 传递给请求中的 Authorization 请求头。

fetch('http://<base-url>/api/method/frappe.auth.get_logged_user', {
    headers: {
        'Authorization': 'token api_key:api_secret'
    }
})
.then(r => r.json())
.then(r => {
    console.log(r);
})
➜ curl http://<base-url>/api/method/frappe.auth.get_logged_user -H "Authorization: token api_key:api_secret"

您使用这些令牌发出的每个请求都将记录在您在步骤 1 中选择的用户名下。这也意味着将针对该用户检查角色权限。您也可以创建一个仅用于 API 调用的新用户。

2. 基于密码的身份验证

基于密码的身份验证依赖于 Cookie 和会话数据来维持后续请求的身份验证状态。在大多数情况下,您用于发出 REST 调用的库会处理会话数据,但如果它不处理,您应该使用基于令牌的身份验证。

fetch('http://<base-url>/api/method/login', {
    method: 'POST',
    headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
    },
    body: JSON.stringify({
        usr: 'username or email',
        pwd: 'password'
    })
})
.then(r => r.json())
.then(r => {
    console.log(r);
})
➜ curl --cookie-jar snowcookie --request POST "http://<base-url>/api/method/login" -H 'Content-Type: application/json' -H 'Accept: application/json' --data-raw "{ "usr" : "<username>", "pwd": "<password>" }"
{"message":"Logged In","home_page":"/app","full_name":"<user:full_name>","dashboard_route":"/sites"}

➜ curl --cookie snowcookie --request POST "http://<base-url>/api/method/frappe.auth.get_logged_user" -H 'Accept: application/json'
{"message":"<username>"}

3. 访问令牌

请参阅有关如何设置 OAuth 的文档。

在请求头中使用生成的 access_token

fetch('http://<base-url>/api/method/frappe.auth.get_logged_user', {
    headers: {
        'Authorization': 'Bearer access_token'
    }
})
.then(r => r.json())
.then(r => {
    console.log(r);
})

列出文档

要获取某个 DocType 的记录列表,请向 /api/resource/:doctype 发送 GET 请求。默认情况下,它将返回 20 条记录,并且仅获取记录的 name 字段。查询结果可以在响应的 data 字段下找到。

我们将使用 ToDo DocType 来展示以下查询的示例响应。

GET /api/resource/:doctype

响应

{
  "data":[
    {"name":"f765eef382"},
    {"name":"2a26fa1c64"},
    {"name":"f32c68060f"},
    {"name":"9065fa9832"},
    {"name":"419082fc38"},
    {"name":"6234d15099"},
    {"name":"62f2181ee0"},
    {"name":"a50afbbfaa"},
    ...
  ]
}

您可以在 fields 参数中指定要获取的字段。它应该是一个 JSON 数组。

GET /api/resource/:doctype?fields=["field1", "field2"]

响应

{
  "data":[
    {"description":"Business worker talk society. Each try theory prove notice middle. Crime couple trouble guy project hit.","name":"f765eef382"},
    {"description":"This reveal as look near sister. Car staff bar specific address.","name":"2a26fa1c64"},
    {"description":"Wear bag some walk. Movie partner new class tough run. Brother Democrat imagine.","name":"f32c68060f"},
    {"description":"Break laugh apply reveal new now focus heavy. Outside local staff research total. Else point try despite.","name":"9065fa9832"},
    {"description":"Truth reduce baby artist actually model. Cost phone us others himself wife almost. Language thing wonder share talk. Factor glass significant could window certain yet.","name":"419082fc38"},
    {"description":"Tv memory understand opportunity window beat physical.","name":"6234d15099"},
    {"description":"Should floor situation in response sell. Our assume company mean red majority shoulder.","name":"62f2181ee0"},
    {"description":"Performance seem sign recent. Court form me tonight simple trouble. Address job garden play teach. Happy speech amount offer change then.","name":"a50afbbfaa"},
    ...
  ]
}

您可以在 expand 参数中指定要展开的字段。它应该是一个 JSON 数组。

GET /api/resource/:doctype?expand=["priority"]

响应

{
  "data":[
    {
        "name":"f765eef382"
        "priority": {
            "name":"a1b2c3", 
            "title": "Medium", 
            "creation": "2025-11-05 19:02:19.106966",
        },
    },
    {
        "name":"f765eef393"
        "priority": {
            "name":"a1b2c4", 
            "title": "High", 
            "creation": "2025-11-05 20:02:19.106966",
        },
    },
    ...
  ]
}

您可以通过传递 filters 参数来过滤记录。过滤器应该是一个数组,其中每个过滤器的格式为:[field, operator, value]

GET /api/resource/:doctype?filters=[["field1", "=", "value1"], ["field2", ">", "value2"]]

响应

{
  "data":[
    {"name":"f765eef382"},
    {"name":"2a26fa1c64"},
    {"name":"f32c68060f"},
    {"name":"9065fa9832"},
    {"name":"419082fc38"},
    {"name":"6234d15099"},
    {"name":"62f2181ee0"},
    {"name":"a50afbbfaa"},
    ...
  ]
}

filters 参数使用 AND SQL 运算符连接所有指定的过滤器,如果您需要 OR 过滤器,则可以使用 or_filters 参数。or_filters 的语法与 `filters` 相同。

您还可以提供排序字段和排序顺序。其格式应为 fieldname ascfieldname desc。空格应进行 URL 编码。在下面一行中,我们假设字段名为 title

GET /api/resource/:doctype?order_by=title%20desc

您还可以通过提供 limit_startlimit_page_length 参数来对结果进行分页。

GET /api/resource/:doctype?limit_start=5&limit_page_length=10

响应

{
  "data": [
    {"name":"6234d15099"},
    {"name":"62f2181ee0"},
    {"name":"a50afbbfaa"},
    {"name":"aa12a5cf71"},
    {"name":"6ac9800d4e"},
    {"name":"4bcf8b701c"},
    {"name":"aee15f4c20"},
    {"name":"6ba753afef"},
    ...
  ]
}

limitlimit_page_length 的别名,用于在版本 13 中访问 /api/resource。这意味着以下请求也应返回与上述查询相同的响应。

GET /api/resource/:doctype?limit_start=5&limit=10

默认情况下,您将收到 List[dict] 格式的数据。您可以通过传递 as_dict=False 来以 List[List] 格式检索数据。

GET /api/resource/:doctype?limit_start=5&limit=5&as_dict=False

响应

{
  "data": [
    ["6234d15099"],
    ["62f2181ee0"],
    ["a50afbbfaa"],
    ["aa12a5cf71"],
    ["6ac9800d4e"]
  ]
}

要调试为您的请求构建的查询,您可以在请求中传递 debug=True。这将在响应的 exc 字段下返回已执行的查询和执行时间。

GET /api/resource/:doctype?limit_start=10&limit=5&debug=True

响应

{
  "data": [
    {"name":"4bcf8b701c"},
    {"name":"aee15f4c20"},
    {"name":"6ba753afef"},
    {"name":"f4b7e24abc"},
    {"name":"bd9156096c"}
  ],
  "exc": "[\"select `tabToDo`.`name`\\n\\t\\t\\tfrom `tabToDo`\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t order by `tabToDo`.`modified` DESC\\n\\t\\t\\tlimit 5 offset 10\", \"Execution time: 0.0 sec\"]"
}

CRUD 操作

Frappe 会自动为所有 DocType 生成用于 CRUD 操作的 REST 端点。请确保在您的请求中设置以下请求头,以便获得正确的 JSON 响应。

{
    "Accept": "application/json",
    "Content-Type": "application/json",
}

创建

通过向 /api/resource/:doctype 发送 POST 请求来创建新文档。在请求体中发送 JSON 格式的文档。

POST /api/resource/:doctype

# Body
{"description": "New ToDo"}

响应

{
  "data": {
    "name": "af2e2d0e33",
    "owner": "Administrator",
    "creation": "2019-06-03 14:19:00.281026",
    "modified": "2019-06-03 14:19:00.281026",
    "modified_by": "Administrator",
    "idx": 0,
    "docstatus": 0,
    "status": "Open",
    "priority": "Medium",
    "description": "New ToDo",
    "doctype": "ToDo"
  }
}

读取

通过向 /api/resource/:doctype/:name 发送 GET 请求来获取文档。

GET /api/resource/:doctype/:name

响应

{
  "data": {
    "name": "bf2e760e13",
    "owner": "Administrator",
    "creation": "2019-06-03 14:19:00.281026",
    "modified": "2019-06-03 14:19:00.281026",
    "modified_by": "Administrator",
    "idx": 0,
    "docstatus": 0,
    "status": "Open",
    "priority": "Medium",
    "description": "
<p>Test description</p>",
    "doctype": "ToDo"
  }
}

通过向 /api/resource/:doctype/:name?expand_links=True 发送 GET 请求来展开所有链接字段。

GET /api/resource/:doctype/:name?expand_links=True

响应

{
  "data": {
    "name": "bf2e760e13",
    "owner": "Administrator",
    "creation": "2019-06-03 14:19:00.281026",
    "modified": "2019-06-03 14:19:00.281026",
    "modified_by": "Administrator",
    "idx": 0,
    "docstatus": 0,
    "status": "Open",
    "priority": {
        "name":"a1b2c3", 
        "title": "Medium", 
        "creation": "2025-11-05 19:02:19.106966",
    },
    "description": "
<p>Test description</p>",
    "doctype": "ToDo"
  }
}

更新

通过向 /api/resource/:doctype/:name 发送 PUT 请求来更新文档。您无需发送整个文档,只需发送要更新的字段即可。

PUT /api/resource/:doctype/:name

# Body
{"description": "New description"}

响应

{
  "data": {
    "name": "bf2e760e13",
    "owner": "Administrator",
    "creation": "2019-06-03 14:19:00.281026",
    "modified": "2019-06-03 14:21:00.785117",
    "modified_by": "Administrator",
    "idx": 0,
    "docstatus": 0,
    "status": "Open",
    "priority": "Medium",
    "description": "New description",
    "doctype": "ToDo"
  }
}

删除

通过向 /api/resource/:doctype/:name 发送 DELETE 请求来删除文档。

DELETE /api/resource/:doctype/:name

响应

{"message": "ok"}

远程方法调用

Frappe 允许您使用 REST API 触发任意的 Python 方法来处理自定义逻辑。这些方法必须被标记为 白名单 才能通过 REST 访问。

要运行位于 frappe.auth.get_logged_user 的白名单 Python 方法,请向端点 /api/method/frappe.auth.get_logged_user 发送请求。

GET /api/method/frappe.auth.get_logged_user

响应

{
  "message": "[email protected]"
}
  • 如果您的方法返回一些值,您应该发送一个 GET 请求。
  • 如果您的方法更改了数据库的状态,请使用 POST。在成功的 POST 请求之后,框架将自动调用 frappe.db.commit() 将更改提交到数据库。
  • 成功的响应将返回一个包含 message 键的 JSON 对象。
  • 出错的响应将返回一个包含 exc 键的 JSON 对象,该键包含堆栈跟踪,以及包含所抛出异常的 exc_type 键。
  • 方法的返回值将被转换为 JSON 并作为响应发送。

文件上传

有一个专门的方法 /api/method/upload_file,它接受二进制文件数据并将其上传到系统中。

以下是它的 curl 命令:

➜ curl -X POST \
  http://<base-url>/api/method/upload_file \
  -H 'Accept: application/json' \
  -H 'Authorization: token xxxx:yyyy' \
  -F file=@/path/to/file/file.png

如果您使用客户端 Javascript 上传文件,您可以将上传的文件附加为 FormData 并发送 XHR 请求。以下是 Frappe Desk 中的实现代码。

钩子

钩子(Hooks)允许你“挂钩”到 Frappe 框架核心部分的功能和事件中。此页面记录了框架提供的所有钩子。

跳转到 Frappe 中所有可用钩子的列表。

钩子是如何工作的?

钩子是核心代码中的一些位置,允许应用覆盖标准实现或对其进行扩展。钩子定义在你应用的 hooks.py 文件中。

让我们通过示例来学习。在你应用的 hooks.py 文件中添加以下钩子。

test_string = "value"
test_list = ["value"]
test_dict = {
    "key": "value"
}

现在,通过运行命令 bench --site sitename console 打开 Python 控制台,并运行以下代码行:

❯ bench --site sitename console
Apps in this namespace:
frappe, frappe_docs

In [1]: frappe.get_hooks("test_string")
Out[1]: ["value"]

In [2]: frappe.get_hooks("test_dict")
Out[2]: {"key": ["value"]}

In [3]: frappe.get_hooks("test_list")
Out[3]: ["value"]

当你调用 frappe.get_hooks 时,它会将列表中的所有值进行转换。这意味着如果钩子在多个应用中定义,则会从这些应用中收集值。这就是实现钩子级联特性的方式。

现在,钩子值可以通过不同方式使用。例如,使用 app_include_js 引入 JS 资源时,会包含所有值。但对于覆盖白名单方法,则使用列表中的最后一个值。

因此,钩子的实现完全取决于功能作者打算如何使用它。

如何解决钩子冲突?

钩子使用“最后写入者获胜”策略进行解析。站点上最后安装的应用将拥有最高优先级。

  • 当钩子覆盖现有行为(如覆盖类)时,只有来自最后安装的应用的覆盖才会生效。
  • 当钩子扩展行为时,扩展将按照在站点上的安装顺序应用。

如果你需要更改此顺序,可以前往“已安装应用”页面,并点击“更新钩子解析顺序”。

应用元数据

这些是在你创建新应用时自动生成的。大多数情况下,你无需更改此处任何内容。

  1. app_name – 应用的 slug 化名称
  2. app_title – 可展示的应用名称
  3. app_publisher
  4. app_description
  5. app_version
  6. app_icon
  7. app_color

JavaScript / CSS 资源

以下钩子允许你在站点的各个部分注入静态 JS 和 CSS 资源。

后台工作台

这些钩子允许你在渲染后台工作台的 desk.html 中注入 JS / CSS。

# injected in desk.html
app_include_js = "assets/js/app.min.js"
app_include_css = "assets/js/app.min.css"

# All of the above support a list of paths too
app_include_js = ["assets/js/app1.min.js", "assets/js/app2.min.js"]

门户网站

这些钩子允许你在渲染门户网站的 web.html 中注入 JS / CSS。

# injected in the web.html
web_include_js = "assets/js/app-web.min.js"
web_include_css = "assets/js/app-web.min.css"
# All of the above support a list of paths too
web_include_js = ["assets/js/web1.min.js", "assets/js/web2.min.js"]

网页表单

这些钩子允许你在用于渲染网页表单的 web_form.html 中添加静态 JS 和 CSS 资源。这些仅适用于标准网页表单。

webform_include_js = {"ToDo": "public/js/custom_todo.js"}
webform_include_css = {"ToDo": "public/css/custom_todo.css"}

对于用户创建的网页表单,你可以直接在表单本身中编写脚本。

页面

这些钩子允许你在标准后台页面中注入 JS 资源。

page_js = {"page_name" : "public/js/file.js"}

例如,后台作业是 Frappe 框架核心模块中的一个标准页面。要在该页面中添加自定义行为,你可以在自定义应用中添加一个 JS 文件 custom_app/public/js/custom_background_jobs.js,并在你的钩子文件中添加以下代码行。

custom_app/hooks.py

page_js = {"background_jobs": "public/js/custom_background_jobs.js"}

声音

Frappe 附带了一组音频通知,用于成功操作、文档提交、错误等事件。你可以使用 sounds 钩子添加自己的声音。

app/hooks.py

sounds = [
    {"name": "ping", "src": "/assets/app/sounds/ping.mp3", "volume": 0.2}
]

你可以使用客户端工具方法播放你添加的声音:

frappe.utils.play_sound("ping")

安装钩子

这些钩子允许你在应用安装之前和之后运行代码。例如,ERPNext 定义了这些。

# python module path
before_install = "app.setup.install.before_install"
after_install = "app.setup.install.after_install"
after_sync = "app.setup.install.after_sync"

app/setup/install.py

# will run before app is installed on site
def before_install():
    pass

# will run after app is installed on site
def after_install():
    pass

# will run after app fixtures are synced
def after_sync():
    pass

卸载钩子

这些钩子允许你在应用卸载之前和之后运行代码。

app/hooks.py

before_uninstall = "app.setup.uninstall.before_uninstall"
after_uninstall = "app.setup.uninstall.after_uninstall"

app/setup/uninstall.py

# will run before app is uninstalled from site
def before_uninstall():
    pass

# will run after app is uninstalled from site
def after_uninstall():
    pass

迁移钩子

这些钩子允许你在通过命令 bench --site sitename migrate 在站点上运行迁移之前和之后运行代码。

app/hooks.py

before_migrate = "app.migrate.before_migrate"
after_migrate = "app.migrate.after_migrate"

app/migrate.py

def after_migrate():
    # run code after site migration
    pass

构建钩子

此钩子允许你通过 bench build 命令扩展构建系统。它在完成 bench 上所有应用的构建后运行。构建钩子允许你在 bench build 结束时,在资源和翻译编译完成后运行代码。

after_build

在你自己的应用构建时运行。用于对你的应用的资源进行后处理。

app/hooks.py

after_build = "app.build.after_build"

app/build.py

def after_build() -> None:
    pass

after_app_build

在每次构建时运行,无论正在构建哪些应用。当您的应用为其他应用生成资源时使用它 – 例如,Frappe Studio 编译其他应用内附带的 studio 前端。

app/hooks.py

after_app_build = "papp.build.after_app_build"
**app/hooks.py**

<pre><code>after_app_build = "app.build.after_app_build"

app/build.py

def after_app_build(built_apps: list[str]) -> None:
    for app in built_apps:
        pass

**app/build.py**

python def after_app_build(built_apps: list[str]) -> None: for app in built_apps: pass # `built_apps` is the value of `–app`/`–apps`, or all apps on the bench when # `bench build` runs without arguments.

测试钩子

此钩子允许您在站点上运行测试之前执行代码。您可以使用此钩子向数据库添加种子数据,这些数据将可用于您的测试。

app/hooks.py

before_tests = "app.tests.before_tests"

app/migrate.py

def before_tests():
    # add seed data to the database
    pass

文件钩子

这些钩子允许您更改处理用户上传文件的实现方式。

app/hooks.py

before_write_file = "app.overrides.file.before_write"
write_file = "app.overrides.file.write_file"
delete_file_data_content = "app.overrides.file.delete_file"

app/overrides/file.py

# will run before file is written to disk
def before_write():
    pass

# will override the implementation of writing file to disk
# can be used to upload files to a CDN instead of writing
# the file to disk
def write_file():
    pass

# will override the implementation of deleting file from disk
# can be used to delete uploaded files from a CDN instead of
# deleting file from disk
def delete_file():
    pass

电子邮件钩子

这些钩子允许您更改默认电子邮件模块中发送电子邮件和设置默认发件人地址的实现方式。

app/hooks.py

override_email_send = "app.overrides.email.send"
get_sender_details = "app.overrides.email.get_sender_details"

默认情况下,frappe 在所有电子邮件中使用当前登录用户的姓名和 ID 作为发件人详细信息。这可以通过 get_sender_details 钩子覆盖。如果您想通过使用第三方服务器或应用发送电子邮件来扩展电子邮件模块的功能,则可以使用 override_email_send 钩子。此钩子会将所有电子邮件信息(发件人、收件人、内容(mime))发送到自定义应用中的函数。

app/overrides/email.py

# will be edited as "John Doe <[email protected]>"
def get_sender_details():
    return "John Doe", "[email protected]"

# self - EmailQueue object refrence for updating status
def send(self, sender, recipient, msg):
    # smtp or http request
    self.update_status("Sending")

注意:您需要根据邮件提供商/服务器返回的 webhook 响应,在自定义应用中处理电子邮件队列的状态更改

扩展启动信息

成功登录后,Desk 会被注入一个名为 bootinfo 的全局值字典。bootinfo 在 Javascript 中作为全局对象 frappe.boot 可用。

bootinfo 字典包含许多值,包括:

  • 系统默认值
  • 通知状态
  • 权限
  • 用户设置
  • 语言和时区信息

您可以通过 extend_bootinfo 钩子添加对您的应用有意义的全局值。

# python module path
extend_bootinfo = "app.boot.boot_session"

该方法以一个参数 bootinfo 调用,您可以直接在其上添加/更新值。

app/boot.py

def boot_session(bootinfo):
 bootinfo.my_global_key = "my_global_value"

现在,您可以在客户端代码的任何位置访问该值。

console.log(frappe.boot.my_global_key)

网站上下文

当门户页面被渲染时,会构建一个包含页面可能需要的所有变量的字典。这个字典也称为 context。您可以使用这些钩子在此字典中添加或修改值。

app/hooks.py

website_context = {
    "favicon": "/assets/app/image/favicon.png"
}
update_website_context = "app.overrides.website_context"

website_context 钩子是一个简单的键值对字典。使用此钩子进行简单的值覆盖。

对于更复杂的场景,您可以使用 update_website_context 钩子,因为它允许您在 Python 方法中操作上下文字典。该方法以一个参数调用,即 context 字典。您可以直接通过修改它来更改上下文,或者返回一个将与 context 合并的字典。

app/overrides.py

def website_context(context):
 context.my_key = "my_value"

网站控制器上下文

Frappe 附带标准网页,如 /404/about。如果您想扩展这些页面的控制器上下文,可以使用 extend_website_page_controller_context 钩子。

app/hooks.py

extend_website_page_controller_context = {
    "frappe.www.404": "app.pages.context_404"
}

上述钩子配置将允许您扩展 404 页面的上下文,以便您可以添加自己的键或修改现有的键。

app/pages.py

def context_404(context):
    # context of the 404 page
    context.my_key = "my_value"

具有动态路由的网页

动态路由是其中包含动态值的路由。

示例:

/profile/<name>
</name>

这里“name”是动态部分,但渲染的是相同的个人资料页面。默认情况下,Frappe 支持来自“网页”文档类型的动态路由。要添加更多动态路由,可以使用 get_web_pages_with_dynamic_routes

app/hooks.py

get_web_pages_with_dynamic_routes = "script.get_web_pages_with_dynamic_routes"

script.py

def get_web_pages_with_dynamic_routes():
 return [{
       "doctype": "Custom Web Page", // Doctype extended from WebsiteGenerator https://frappeframework.com/docs/user/en/guides/portal-development/generators
       "route": "/profile/
<name>",
       "name": "profile-page" // name of the web view document to render
    }, ...]
</name>

网站清除缓存

Frappe 框架缓存大量静态网页以便后续快速渲染。如果您创建了使用缓存值的网页,并且想要使缓存失效,此钩子就是执行此操作的地方。

app/hooks.py

website_clear_cache = "app.overrides.clear_website_cache"

该方法以一个参数 path 调用。当为单个路由清除缓存时,path 被设置,当为所有路由清除缓存时,None 被设置。如果您的缓存是特定于页面的,您需要处理这种情况。

app/overrides.py

def clear_website_cache(path=None):
    if path:
        # clear page related cache
    else:
        # clear all cache

网站重定向

网站重定向允许您定义从一个路由到另一个路由的重定向。当请求源 URL 时,Frappe 将生成 304 重定向响应并重定向到目标 URL。您可以重定向普通 URL,也可以使用正则表达式来匹配您的 URL。

app/hooks.py

website_redirects = [
    {"source": "/compare", "target": "/comparison"},
    {"source": "/docs(/.*)?", "target": "https://docs.tennismart.com/\1"},
    {"source": r'/items/item\?item_name=(.*)', "target": '/items/\1', match_with_query_string=True},
]

上述配置将导致以下重定向:

  • /compare/comparison
  • /docs/getting-startedhttps://docs.tennismart.com/getting-started
  • /docs/helphttps://docs.tennismart.com/help
  • /items/item?item_name=rackethttps://docs.tennismart.com/items/racket

网站路由规则

网站路由规则允许您将 URL 映射到自定义控制器。这通常用于为页面生成简洁的 URL。

假设您希望 /projects 路由显示项目列表。这可以通过在 www 文件夹中创建 projects.htmlprojects.py 来实现。

您还希望 /project/<name></name> 路由显示项目页面,其中 name 是动态的。为此,您可以使用 website_route_rules 钩子。

app/hooks.py

website_route_rules = [
    {"from_route": "/projects/
<name>", "to_route": "app/projects/project"},
]
</name>

现在,您可以在 app/projects 文件夹中创建控制器文件。

app/projects/project.py

def get_context(context):
    project_name = frappe.form_dict.name
    project = frappe.get_doc("Project", project_name)
    context.project = project

app/projects/project.html

<h1>{{ project.title }}</h1>
<p>{{ project.description }}</p>

网站路径解析器

Frappe 会执行一些标准的路径解析,例如,任何对 “/profile” 的请求都会在内部转换为 “/me”。可以使用 website_path_resolver 来覆盖此行为。

app/hooks.py

website_path_resolver = "path.to.custom_resolver_method"

注意: 您的自定义解析方法将接收请求的路由,并应返回处理后的路由。

网站 404

当页面未找到时,Frappe 会渲染默认的 /404 路由。您可以使用 website_catch_all 钩子来更改此设置。

app/hooks.py

website_catch_all = "not_found"

上述配置将在发生 404 错误时渲染 /not_found。您需要自行实现模板 www/not_found.html 和控制器 www/not_found.py

默认首页

首页是当您访问站点根 URL(/)时渲染的页面。有多种方法可以配置默认渲染哪个页面作为首页。

默认情况下,首页是 index。因此,frappe 将尝试从 www 文件夹渲染 index.html。这可以使用 homepage 钩子来覆盖。

app/hooks.py

homepage = "homepage"

上述配置将加载 www/homepage.html 作为默认首页。

您还可以通过使用 role_home_page 钩子来设置基于角色的首页。

app/hooks.py

role_home_page = {
    "Customer": "orders",
    "Supplier": "bills"
}

上述配置将使 /orders 成为具有 客户 角色的用户的默认首页,并使 /bills 成为具有 供应商 角色的用户的默认首页。

您还可以通过使用 get_website_user_home_page 钩子对逻辑进行更精细的控制。

app/hooks.py

get_website_user_home_page = "app.website.get_home_page"

app/website.py

def get_home_page(user):
    if is_projects_user(user):
        return "projects"
    if is_partner(user):
        return "partner-dashboard"
    return "index"

如果所有这些钩子都已定义,则 get_website_user_home_page 的优先级将高于其他钩子,而 role_home_page 的优先级将高于 homepage

门户侧边栏

某些门户视图会显示带有链接的侧边栏,以便快速跳转到页面。这些侧边栏项目可以通过钩子进行自定义。

app/hooks.py

portal_menu_items = [
    {"title": "Dashboard", "route": "/dashboard", "role": "Customer"},
    {"title": "Orders", "route": "/orders", "role": "Customer"},
]

上述配置将为具有客户角色的用户添加两个侧边栏链接。

这些侧边栏项目在您的应用程序中是硬编码的,因此无法从 Desk 进行自定义。例如,如果您想临时隐藏某个侧边栏链接,则必须修改代码。

还有另一个名为 standard_portal_menu_items 的钩子允许您执行此操作。在 standard_portal_menu_items 钩子中设置的侧边栏链接将与数据库同步。

app/hooks.py

standard_portal_menu_items = [
    {"title": "Dashboard", "route": "/dashboard", "role": "Website Manager"},
    {"title": "Orders", "route": "/orders", "role": "Website Manager"},
]

上述配置会将侧边栏项目同步到门户设置,之后任何系统用户都可以对其进行编辑。

品牌 HTML

此钩子允许您自定义网站导航栏中的品牌标志。

app/hooks.py

brand_html = '<div><img src="tennismart.png"> TennisMart</div>'

如果定义了 brand_html,它将覆盖导航栏中的默认品牌 HTML。除非您想对其进行版本控制,否则不建议使用钩子来更改品牌标志,否则您可以使用网站设置来更改它。

基础模板

渲染网页时,默认会扩展 templates/base.html。您可以通过覆盖 base_template 钩子来覆盖基础模板。

app/hooks.py

base_template = "app/templates/my_custom_base.html"

您还可以根据路由自定义基础模板。例如,如果您想为所有以 docs/* 开头的路由使用不同的基础模板,则可以使用 base_template_map 钩子。键必须是匹配路由的正则表达式。所有其他路由将回退到默认基础模板。

app/hooks.py

base_template_map = {
    r"docs.*": "app/templates/doc_template.html"
}

集成

这些钩子允许您自定义 Frappe 中第三方集成的行为。

Braintree 成功页面

此钩子允许您在 Braintree 交易成功支付后覆盖默认的重定向 URL。

app/hooks.py

braintree_success_page = "app.integrations.braintree_success_page"

该方法使用一个参数 data 调用,该参数包含付款的元数据。

app/integrations.py

def braintree_success_page(data):
    # data.reference_doctype
    # data.reference_docname
    return "/thank-you"

日历

日历钩子是一个文档类型名称列表,这些名称在 Desk 的日历页面中显示为菜单项,以便快速导航。

app/hooks.py

calendars = ["Appointment"]

清除缓存

此钩子允许您在 Frappe 清除全局缓存时,清除您应用特定的缓存值。

app/hooks.py

clear_cache = "app.cache.clear_cache"

您可以使用此钩子来清除您应用特定的缓存。该方法在调用时不带任何参数。

app/cache.py

def clear_cache():
    frappe.cache().hdel("app_specific_cache")

如果您想设置 Frappe 发送的所有邮件的默认页脚,可以使用 default_mail_footer 钩子。

app/hooks.py

default_mail_footer = """

<div>
 Sent via <a href="https://tennismart.com" target="_blank">TennisMart</a>
</div>
"""

现在,所有邮件的页脚都将显示 通过 TennisMart 发送

会话钩子

这些钩子在用户登录生命周期中被触发。on_login 在成功登录后立即触发,on_session_creation 在会话设置完成后触发,on_logout 在用户注销后触发。

app/hooks.py

on_login = "app.overrides.successful_login"
on_session_creation = "app.overrides.allocate_free_credits"
on_logout = "app.overrides.clear_user_cache"

该方法将使用一个参数 login_manager 被调用。

app/overrides.py

def allocate_free_credits(login_manager):
    # allocate free credits to frappe.session.user
    pass

认证钩子

这些钩子在请求认证期间被触发。可以在此处验证自定义标头、授权标头,用户通过 frappe.set_user() 被验证并映射到请求。使用 frappe.requestfrappe.* 来验证请求并映射用户。

app/hooks.py

auth_hooks = ["app.overrides.validate_custom_jwt"]

该方法将在请求认证期间被调用。

app/overrides.py

def validate_custom_jwt():
    # validate jwt from header, verify signature, set user from jwt.
    pass

使用此方法检查传入的请求标头,验证标头并将用户映射到请求。如果标头验证失败,请勿抛出错误以继续使用其他钩子。未经验证的请求默认被视为“访客”请求。您可以使用第三方服务器、共享数据库或任何其他选择来验证和映射请求与用户。

固定数据

固定数据是当您安装和更新站点时,通过 JSON 文件同步的数据库记录。

假设您希望在安装应用时在数据库中创建一组类别。为此,请在您的本地站点中创建这组类别,并将文档类型名称添加到 fixtures 钩子中。

fixtures = [
    # export all records from the Category table
    "Category"
]

现在,运行以下命令:

bench --site sitename export-fixtures

此命令将为每个文档类型创建一个 JSON 文件,其中包含生成记录列表所需的数据。您可以通过创建一个新站点并在该站点上安装您的应用来测试此功能。

您还可以为导出记录添加条件。

fixtures = [
    # export all records from the Category table
    "Category",
    # export only those records that match the filters from the Role table
    {"dt": "Role", "filters": [["role_name", "like", "Admin%"]]},
]

某些字段仅供内部使用。系统会自动设置并保持这些字段的最新状态。这些字段不会被导出:modified_bycreationowneridxlftrgt。对于子表记录,以下字段不会被导出:docstatusdoctypemodifiedname

文档钩子

修改列表查询

您可以通过使用 permission_query_conditions 钩子添加自定义匹配条件,来自定义 DocType 记录列表的查询方式。此匹配条件必须是 SQL 查询的有效 WHERE 子句片段。

app/hooks.py

permission_query_conditions = {
    "ToDo": "app.permissions.todo_query",
}

该方法使用单个参数 user 被调用,该参数可以是 None。该方法应返回一个字符串,该字符串是有效的 SQL WHERE 子句。

app/permissions.py

def todo_query(user):
    if not user:
        user = frappe.session.user
    # todos that belong to user or assigned by user
    return "(`tabToDo`.owner = {user} or `tabToDo`.assigned_by = {user})".format(user=frappe.db.escape(user))

现在,如果您使用 frappe.db.get_list 方法,您的 WHERE 子句将被附加到查询中。

todos = frappe.db.get_list("ToDo", debug=1)

# output
'''
select `tabToDo`.`name`
from `tabToDo`
where ((`tabToDo`.owner = '[email protected]' or `tabToDo`.assigned_by = '[email protected]'))
order by `tabToDo`.`modified` DESC
'''

此钩子只会影响 frappe.db.get_list 方法的结果,而不会影响 > frappe.db.get_all

文档权限

您可以使用 has_permission 钩子修改任何 DocType 的 doc.has_permission 文档方法的行为,并添加自定义权限检查逻辑。

app/hooks.py

has_permission = {
    "Event": "app.permissions.event_has_permission",
}

该方法将接收 docuserpermission_type 作为参数。它应返回 True 或一个 False 值。如果返回 None,它将回退到默认行为。

app/permissions.py

def event_has_permission(doc, user=None, permission_type=None):
    # when reading a document allow if event is Public
    if permission_type == "read" and doc.event_type == "Public":
        return True

    # when writing a document allow if event owned by user
    if permission_type == "write" and doc.owner == user:
        return True

    return False

扩展 DocType 类

注意:此功能在 v16+ 中可用

您可以使用 extend_doctype_class 钩子扩展标准文档类型的类。此钩子允许您向现有的 DocType 类添加属性、方法和功能,而无需完全覆盖它们,从而使多个应用能够扩展同一个 DocType 类。

app/hooks.py

extend_doctype_class = {
    "Address": ["app.extensions.address.AddressMixin"]
}

app/extensions/address.py

from frappe.model.document import Document

class AddressMixin(Document):
    @property
    def full_address(self):
        return f"{self.address_line1}, {self.city}, {self.country}"

    def custom_validation(self):
        # Custom validation logic
        pass

    def validate(self):
        super.validate()
        self.custom_validation()

多个扩展

您可以为同一个 DocType 定义多个扩展。例如,ValidationMixin 可以用于 联系人 以及 地址,而 GeocodingMixin 仅用于 地址

app/hooks.py

extend_doctype_class = {
    "Address": [
        "app.extensions.address.GeocodingMixin",
        "app.extensions.common.ValidationMixin"
    ],
    "Contact": [
        "app.extensions.common.ValidationMixin"
    ]
}

钩子解析顺序

当多个应用为同一个 DocType 定义扩展时,扩展将按照钩子解析顺序应用。如果应用按 frappeapp1app2 的顺序解析,最终的类将是:

class ExtendedAddress(App2Mixin, App1Mixin, Address):
    pass

override_doctype_class 的交互

extend_doctype_class 钩子在 [override_doctype_class](#override-doctype-class) 之上工作。如果同时定义了这两个钩子,扩展将应用于被覆盖的类,而不是基类。

例如,如果 ERPNext 覆盖了地址类,而自定义应用对其进行了扩展:

# ERPNext overrides Address
override_doctype_class = {
    "Address": "erpnext.setup.doctype.address.address.Address"
}

# Custom apps extend it
extend_doctype_class = {
    "Address": ["app1.extensions.Prop1Mixin", "app2.extensions.Prop2Mixin"]
}

# Final class becomes:
class ExtendedAddress(Prop2Mixin, Prop1Mixin, <a href="https://erpnext.yuannext.com">ERPNext</a>Address):
    pass

此钩子非常适合添加虚拟字段、计算属性和自定义方法,而不会干扰核心功能。当您只需要添加功能而不是替换功能时,建议使用此钩子而不是 [override_doctype_class](#override-doctype-class)

覆盖 DocType 类

您可以使用 override_doctype_class 钩子来覆盖/扩展标准 DocType 的类。与 [extend_doctype_class](#extend-doctype-class) 不同,此钩子会完全替换原始类。当多个应用覆盖同一个 DocType 类时,这可能会导致问题。在 v16+ 中,建议改用 [extend_doctype_class](#extend-doctype-class)

app/hooks.py

override_doctype_class = {
    "ToDo": "app.overrides.todo.CustomToDo"
}

app/overrides/todo.py

from frappe.desk.doctype.todo.todo import ToDo

class CustomToDo(ToDo):
    def on_update(self):
        self.my_custom_code()
        super().on_update()

    def my_custom_code(self):
        pass

建议您扩展 DocType 的标准类,否则您将不得不自行实现所有核心功能。

覆盖表单脚本

您可以使用 doctype_js 钩子来覆盖/扩展标准表单脚本。

app/hooks.py

doctype_js = {
    "ToDo": "public/js/todo.js",
}

app/public/js/todo.js

frappe.ui.form.on("Todo", {
    refresh: function(frm) {
        frm.trigger("my_custom_code");
    },
    my_custom_code: function(frm){
        console.log(frm.doc.name)
    }
});

app/public/todo.js 中定义的事件/函数将扩展 ToDo DocType 的标准表单脚本中的事件/函数。

CRUD 事件

您可以使用 doc_events 钩子来挂钩任何 DocType 的各种 CRUD 事件。

app/hooks.py

doc_events = {
    "*": {
        # will run after any DocType record is inserted into database
        "after_insert": "app.crud_events.after_insert_all"
    },
    "ToDo": {
        # will run before a ToDo record is inserted into database
        "before_insert": "app.crud_events.before_insert_todo",
    }
}

该方法将接收文档和方法名称作为参数。

app/crud_events.py

def after_insert_all(doc, method=None):
    pass

def before_insert_todo(doc, method=None):
    pass

有关所有可用钩子的列表,请参阅控制器钩子 >。

覆盖白名单方法

白名单方法是在 REST 端点上可访问并被客户端使用的 Python 方法。您可以使用 override_whitelisted_methods 钩子来覆盖核心框架中标准白名单方法。

app/hooks.py

override_whitelisted_methods = {
    "frappe.client.get_count": "app.whitelisted.custom_get_count"
}

该方法应具有与原始方法相同的签名。

app/whitelisted.py

def custom_get_count(doctype, filters=None, debug=False, cache=False):
    # your custom implementation of the standard get_count method provided by frappe
    pass

要在删除文档时忽略对特定 DocType 的链接,您可以在 ignore_links_on_delete 钩子中指定它们,如下所示:

app/hooks.py

ignore_links_on_delete = ["Communication", "ToDo"]

表单时间线

文档表单视图的时间线部分显示了对该文档执行的操作的审计跟踪,例如查看、值更改、评论和相关通信等。

除了这些标准操作之外,有时您可能需要添加自己的自定义操作。您可以通过 additional_timeline_content 钩子来实现。

additional_timeline_content: {
    # show in each document's timeline
    "*": ["app.timeline.all_timeline"]
    # only show in ToDo's timeline
    "ToDo": ["app.timeline.todo_timeline"]
}

该方法将接收 doctype 和 docname 作为参数。您可以执行查询并返回与该文档相关的操作,作为字典列表,如示例所示。列表中的每个字典必须有一个 creation 值,该值将用于对时间线中的项目进行排序。

def todo_timeline(doctype, docname):
    # this method should return a list of dicts
    return [
        {
             # this will be used to sort the content in the timeline
            "creation": "22-05-2020 18:00:00",
            # this JS template will be rendered in the timeline
            "template": "custom_timeline_template",
            # this data will be passed to the template.
            "template_data": {"key": "value"},
        },
        ...
    ]

调度器事件

您可以使用 scheduler_events 钩子通过调度器事件在后台定期运行任务。

app/hooks.py

scheduler_events = {
    "hourly": [
        # will run hourly
        "app.scheduled_tasks.update_database_usage"
    ],
}

app/scheduled_tasks.py

def update_database_usage():
    pass

hooks.py 中更改任何计划事件后,您需要运行 bench migrate 才能使更改生效。

可用事件

  • hourlydailyweeklymonthly

这些事件将分别每小时、每天、每周和每月触发一次。

  • hourly_longdaily_longweekly_longmonthly_long

与上述相同,但这些作业在长工作进程中运行,适用于长时间运行的作业。

  • all

all 事件每 60 秒触发一次。这可以通过 common_site_config.json 中的 scheduler_tick_interval 键进行配置。

  • cron

一个有效的 cron 字符串,可由 croniter 解析。

使用示例:

scheduler_events = {
    "daily": [
        "app.scheduled_tasks.manage_recurring_invoices"
    ],
    "daily_long": [
        "app.scheduled_tasks..take_backups_daily"
    ],
    "cron": {
        "15 18 * * *": [
            "app.scheduled_tasks..delete_all_barcodes_for_users"
        ],
        "*/6 * * * *": [
            "app.scheduled_tasks..collect_error_snapshots"
        ],
        "annual": [
            "app.scheduled_tasks.collect_error_snapshots"
        ]
    }
}

Jinja 自定义

Frappe 在 Jinja 模板中提供了一系列全局实用方法。要添加您自己的方法和过滤器,您可以使用 jinja 钩子。

app/hooks.py

jinja = {
    "methods": [
        "app.jinja.methods",
        "app.utils.get_fullname"
    ],
    "filters": [
        "app.jinja.filters",
        "app.utils.format_currency"
    ]
}

app/jinja/methods.py

def sum(a, b):
    return a + b

def multiply(a, b):
    return a * b

如果路径是模块路径,则该模块中的所有方法都将被添加。

app/utils.py

def get_fullname(user):
    first_name, last_name = frappe.db.get_value("User", user, ["first_name", "last_name"])
    return first_name + " " + last_name

def format_currency(value, currency):
    return currency + " " + str(value)

现在,您可以在 Jinja 模板中使用这些实用程序,如下所示:

<h1>Hi, {{ get_fullname(frappe.session.user) }}</h1>
<p>Your account balance is {{ account_balance | format_currency("INR") }}</p>
<p>1 + 2 = {{ sum(1, 2) }}</p>

防止链接文档自动取消

要防止特定 DocType 的文档在取消任何链接文档时被自动取消,您可以使用 auto_cancel_exempted_doctypes 钩子。

app/hooks.py

auto_cancel_exempted_doctypes = ["Payment Entry"]

在上面的示例中,如果任何与付款条目链接的文档(例如销售发票)被取消,它将跳过链接的付款条目文档的自动取消。

通知配置

通知配置钩子用于自定义工作台中通知下拉列表中显示的项目。它可以通过 notification_config 钩子进行配置。

app/hooks.py

notification_config = "app.notification.get_config"

该方法在没有任何参数的情况下被调用。

app/notification.py

def get_config():
 return {
        "for_doctype": {
            "Issue": {"status":"Open"},
            "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"
        }
    }

上述配置包含三个部分:

  1. 上述配置中的 for_doctype 部分会将状态为“开启”的“问题”或“客户问题”标记为未读
  2. for_module_doctypes 将文档类型映射到模块的未读计数。
  3. for_module 将模块映射到获取其未读计数的函数。这些函数在调用时不带任何参数。

所需应用

在构建应用时,您可能会创建基于其他应用构建的应用。为确保在有人安装您的应用时同时安装依赖应用,您可以使用 required_apps 钩子。

app/hooks.py

required_apps = ["erpnext"]

上述配置将确保在有人安装您的应用时,erpnext 也会被安装。

用户数据保护与隐私

Frappe 内置了个人数据下载和个人数据删除等用户数据隐私功能。哪些数据构成个人数据可由应用发布者在应用的 hooks.py 文件中以 user_data_fields 的形式定义。

app/hooks.py

user_data_fields = [
    {"doctype": "Access Log"},
    {"doctype": "Comment", "strict": True},
    {
        "doctype": "Contact",
        "filter_by": "email_id",
        "rename": True,
    },
    {"doctype": "Contact Email", "filter_by": "email_id"},
    {
        "doctype": "File",
        "filter_by": "attached_to_name",
        "redact_fields": ["file_name", "file_url"],
    },
    {"doctype": "Email Unsubscribe", "filter_by": "email", "partial": True},
]

包含用户数据的文档类型应按照上述格式映射到此钩子下。当用户提出数据删除或下载请求时,将利用此钩子映射到指定的文档类型。可用于修改文档的选项如下:

字段 说明
doctype 包含用户数据的文档类型。
filter_by 用于筛选文档的文档字段。如果未设置,默认为 owner
partial 如果设置,将解析所有文本字段并删除用户的姓名和用户名引用。
redact_fields 需要删除的字段。如果未指定,则视为对所有文本字段进行部分数据删除。
rename 如果文档名称包含用户数据,设置此字段以重命名文档,使其匿名化。
strict 如果设置为 True,将从当前文档类型的所有文档中删除任何用户数据。如果未设置,默认为 False,这意味着仅筛选用户为所有者的文档。

注意:个人数据下载仅使用 user_data_fields 中定义的文档类型和筛选字段。

相关主题:

  1. 个人数据删除
  2. 个人数据下载

注册表单模板

如果您想向注册表单添加额外字段,可以使用此钩子。创建一个包含自定义注册表单的模板文件。将此模板传递给自定义注册钩子。

signup_form_template = "school/templates/signup-form.html"

注意:如果您希望在注册表单中添加自定义字段,则需要在用户文档类型中添加额外字段。您必须使用固定数据(fixtures)来添加这些字段。此外,您还需要为这个注册表单编写自己的提交处理程序,并在服务器端编写一个用于注册用户的函数。这样,您还可以为您添加的自定义字段编写验证逻辑。

短信钩子

这些钩子允许您通过集成第三方短信服务提供商或实现自定义短信逻辑,来自定义 Frappe 中的短信发送功能。

发送短信

此钩子允许您覆盖通知、群发短信和其他常规短信功能的默认短信发送逻辑。

app/hooks.py

send_sms = "app.overrides.sms.send_sms"

调用该方法时会传入短信详情,包括收件人、消息内容和其他元数据。

app/overrides/sms.py

def send_sms(receiver_list, msg, sender=None, success_msg=True):
    """
    Override default SMS sending logic

    Args:
        receiver_list: List of mobile numbers or single mobile number
        msg: SMS message content
        sender: Sender ID (optional)
        success_msg: Whether to show success message (optional)
    """

    # Send SMS via custom provider
    response = custom_sms_provider.send_sms(
        to=receiver_list,
        message=msg,
        from_number=sender
    )

    if response.status_code == 200:
        frappe.msgprint(_("SMS sent successfully"))
        return True
    else:
        frappe.throw(_("Failed to send SMS"))
        return False

通过短信发送令牌

此钩子允许您覆盖双因素认证和移动登录流程中的短信 OTP 发送逻辑。

app/hooks.py

send_token_via_sms = "app.overrides.sms.send_token_via_sms"

调用该方法时会传入用于认证目的的 OTP 详情。

app/overrides/sms.py

def send_token_via_sms(otpsecret, token=None, phone_no=None):
   """
    Generate OTP and send using local send_otp function.
    :param otpsecret: OTP secret for generating HOTP
    :param token: Token to use for HOTP generation
    :param phone_no: Phone number to send OTP to
    """
    if not phone_no:
        return False
    try:
        hotp = pyotp.HOTP(otpsecret)
        otp_code = hotp.at(token_int)
        result = send_otp(
            number=phone_no,
            otp_length=len(otp_code),
            otp_expiry=5,  # 5 minutes expiry
            otp=otp_code
        )
        return result.get("success", False)
    except Exception as e:
        frappe.log_error(
            message=f"Failed to send OTP: {str(e)}",
            title="OTP Error"
        )
        return False

可用钩子列表

钩子名称 说明
additional_timeline_content 表单时间线
after_install 安装钩子
after_migrate 迁移钩子
after_sync 安装钩子
app_include_css 工作台资源
app_include_js 工作台资源
app_logo_url 应用元数据
app_title 应用元数据
auto_cancel_exempted_doctypes 防止自动取消
base_template_map 基础模板
base_template 基础模板
before_install 安装钩子
before_migrate 迁移钩子
before_tests 测试钩子
before_write_file 文件钩子
bot_parsers 已弃用
braintree_success_page Braintree 成功页面
brand_html 品牌 HTML
calendars 日历
clear_cache 清除缓存
communication_doctypes
default_mail_footer 默认邮件页脚
delete_file_data_content 文件钩子
doc_events 文档增删改查事件
doctype_js 覆盖表单脚本
domains
dump_report_map 已弃用
extend_bootinfo 扩展启动信息
extend_website_page_controller_context 网站控制器上下文
filters_config
fixtures 测试数据
get_site_info
get_translated_dict
get_website_user_home_page 默认首页
get_web_pages_with_dynamic_routes 带动态路由的网页
has_permission 文档权限
has_website_permission
home_page 默认首页
jenv Jinja 自定义
leaderboards
look_for_sidebar_json
make_email_body_message
notification_config 通知配置
on_login 会话钩子
on_logout 会话钩子
on_print_pdf 打印时
on_session_creation 会话钩子
extend_doctype_class 扩展 DocType 类
override_doctype_class 覆盖 DocType 类
override_doctype_dashboards
override_whitelisted_methods 覆盖白名单方法
ignore_links_on_delete 删除时忽略链接
permission_query_conditions 修改列表查询
portal_menu_items 门户侧边栏
required_apps 所需应用
role_home_page 默认首页
scheduler_events 计划任务事件
setup_wizard_complete
setup_wizard_exception
setup_wizard_requires
setup_wizard_stages
setup_wizard_success
signup_form_template 注册表单模板
sounds 提示音
standard_portal_menu_items 门户侧边栏
standard_queries 标准查询
send_sms 短信钩子
send_token_via_sms 短信钩子
template_apps
translated_languages_for_website
translator_url
treeviews 默认使用树形视图(而非列表视图)作为默认视图的 DocType
update_website_context 网站上下文
user_privacy_documents 已弃用(改用 user_data_fields 钩子)
user_data_fields 用户数据保护与隐私
web_include_css 门户资源
web_include_js 门户资源
website_catch_all 网站 404 页面
website_clear_cache 网站缓存清理
website_context 网站上下文
website_generators 已弃用(请改用 DocType 中的“具有网页视图”选项)
website_redirects 网站重定向
website_route_rules 网站路由规则
website_user_home_page 已弃用(改用 homepage 钩子)
welcome_email
write_file_keys 已弃用
write_file 文件钩子

搜索

在 Frappe 中进行搜索由 Search 模块管理。它是 Whoosh 的封装,Whoosh 是一个用 Python 编写的全文搜索库。

您可以扩展 FullTextSearch 类,为特定需求创建搜索类。例如,WebsiteSearch 是一个用于索引公开网页并暴露搜索功能的封装。

FullTextSearch

每个 FullTextSearch(FTS)实例都持有一个由类本身定义的 Schema。这意味着,特定的 FTS 实现会有其特定的 schema。如果您希望使用不同的 schema 进行索引,可以创建新的实现。除此之外,FTS 类还提供了其他控制器,用于创建、更新和查询索引。

扩展 FTS 类

初始化基于 FTS 的类时,您需要提供一个索引名称。实例化时,会初始化以下参数:

  • index_name:提供的索引名称。
  • index_path:索引在站点文件夹中的路径。
  • schema:由 get_schema 函数返回。
  • id:用于在索引中识别文档的 ID。

实例化后,您可以运行 build 函数。它会从 get_items_to_index 获取所有文档,这些文档是符合定义 schema 的 frappe._dict(frappe 字典)列表。然后,这些文档会被添加到索引中并写入文件。

您可以使用 FTS 类的 search 方法搜索索引。这些函数在 API 参考文档中有详细说明。

博客的示例实现如下所示:

class BlogWrapper(FullTextSearch):
 # Default Schema
 # def get_schema(self):
 # return Schema(name=ID(stored=True), content=TEXT(stored=True))

 # def get_id(self):
 # return "name"

 def get_items_to_index(self):
 docs = []
 for blog_name in get_all_blogs():
 docs.append(get_document_to_index(blog_name))
 return docs

 def get_document_to_index(self, name):
 blog = frappe.get_doc("Blog Post", name)
 return frappe._dict(name=name, content=blog.content)

 def parse_result(self, result):
 return result["name"]
  • get_items_to_index:获取所有需要索引的路由,包括 www/ 目录下的静态页面以及已发布文档的路由。
  • get_document_to_index:渲染页面并使用 BeautifulSoup 进行解析。
  • parse_result:所有搜索结果都通过此函数进行解析。

好的,请发送需要翻译的英文标题。

在这里,让我们来看看 Frappe 中响应是如何构建的,以及你如何在你的
Frappe 应用或脚本中使用它们。

如果你已经阅读过路由器
文档,你可能已经注意到 Frappe 内部用来根据内容类型
构建响应的 build_response 函数。定义此行为的逻辑属于
frappe.utils.response
模块的一部分,其中 build_response 是该模块的核心内容。

def build_response(response_type=None):
 if "docs" in frappe.local.response and not frappe.local.response.docs:
 del frappe.local.response["docs"]

 response_type_map = {
 "csv": as_csv,
 "txt": as_txt,
 "download": as_raw,
 "json": as_json,
 "pdf": as_pdf,
 "page": as_page,
 "redirect": redirect,
 "binary": as_binary,
 }

 return response_type_map[frappe.response.get("type") or response_type]()

上面的代码片段展示了 build_response 的当前实现,
它映射了不同的函数,这些函数充当不同内容类型的处理器。
让我们更深入地了解一下 Frappe v13 中针对 “download”
response_type 的响应处理器。

def as_raw():
 response = Response()
 response.mimetype = (
 frappe.response.get("content_type")
 or mimetypes.guess_type(frappe.response["filename"])[0]
 or "application/unknown"
 )
 response.headers["Content-Disposition"] = (
 f'{frappe.response.get("display_content_as", "attachment")};'
 f' filename="{frappe.response["filename"].replace(" ", "_")}"'
 ).encode("utf-8")
 response.data = frappe.response["filecontent"]
 return response

根据 Content-Disposition 头的值,
接收响应的浏览器可能会有不同的行为。如果未设置,该值默认为
“attachment”

如果 frappe.response.display_content_as 设置为 “inline”,则表示
内容预期在浏览器中内联显示,即作为
网页或网页的一部分显示,而 “attachment” 则表示内容
将被下载并保存在本地。

要创建一个可以直接下载所需文件的 API 端点,
你可以编写类似以下代码来实现直接下载文件。

@frappe.whitelist()
def download(name):
 file = frappe.get_doc("File", name)
 frappe.response.filename = file.file_name
 frappe.response.filecontent = file.get_content()
 frappe.response.type = "download"
 frappe.response.display_content_as = "attachment"

工具函数

Frappe 框架附带各种实用函数,用于处理常见的操作,例如站点特定的日期时间管理、日期和货币格式化、PDF 生成等等。

这些实用方法可以从 frappe.utils 模块(及其嵌套模块,如 frappe.utils.loggerfrappe.utils.data)中导入到您的 Frappe 应用的任何 Python 文件中。此列表并非详尽无遗,您可以查看框架代码库以了解可用的内容。

now(当前时间)

now()

返回当前日期时间,格式为 yyyy-mm-dd hh:mm:ss

from frappe.utils import now

now() # '2021-05-25 06:38:52.242515'

getdate(获取日期)

getdate(string_date=None)

string_date(yyyy-mm-dd)转换为 datetime.date 对象。如果未提供输入,则返回当前日期。如果 string_date 是无效的日期字符串,则会抛出异常。

from frappe.utils import getdate

getdate() # datetime.date(2021, 5, 25)
getdate('2000-03-18') # datetime.date(2000, 3, 18)

today(今天)

today()

返回当前日期,格式为 yyyy-mm-dd

from frappe.utils import today

today() # '2021-05-25'

add_to_date(日期加减)

add_to_date(date, years=0, months=0, weeks=0, days=0, hours=0, minutes=0, seconds=0, as_string=False, as_datetime=False)

`date`: A string representation or `datetime` object, uses the current `datetime` if `None` is passed
`as_string`: Return as string
`as_datetime`: If `as_string` is True and `as_datetime` is also True, returns a `datetime` string otherwise just the `date` string.

此函数在进行日期/日期时间增减时非常方便,例如,从特定日期/日期时间加上或减去一定数量的天数。

from datetime import datetime # from python std library
from frappe.utils import add_to_date

today = datetime.now().strftime('%Y-%m-%d')
print(today) # '2021-05-21'

after_10_days = add_to_date(datetime.now(), days=10, as_string=True)
print(after_10_days) # '2021-05-31'

add_to_date(datetime.now(), months=2) # datetime.datetime(2021, 7, 21, 15, 31, 18, 119999)
add_to_date(datetime.now(), days=10, as_string=True, as_datetime=True) # '2021-05-31 15:30:23.757661'
add_to_date(None, years=6) # datetime.datetime(2027, 5, 21, 15, 32, 31, 652089)

date_diff(日期差)

date_diff(date_2, date_1)

返回给定两个日期之间的天数差。

from frappe.utils import add_to_date, today, date_diff
date_1 = today()
date_2 = add_to_date(date_1, days=10)

print(date_diff(date_2, date_1)) #10

days_diff(天数差)

days_diff(date_2, date_1)

返回给定两个日期之间的天数差。

from frappe.utils import add_to_date, today, days_diff
date_1 = today()
date_2 = add_to_date(date_1, days=10)

print(days_diff(date_2, date_1)) #10

month_diff(月份差)

month_diff(date_2, date_1)

返回给定两个日期之间的月份差。

from frappe.utils import add_to_date, month_diff
date_1 = "2024-07-01"
date_2 = add_to_date(date_1, days=60)

print(month_diff(date_2, date_1)) #2

pretty_date(友好日期)

pretty_date(iso_datetime)

接受一个 ISO 时间,并返回一个字符串,表示该日期距今有多长时间。这在即时通讯等通信应用中非常常见。

from frappe.utils import pretty_date, now, add_to_date

pretty_date(now()) # 'just now'

# Some example outputs:

# 1 hour ago
# 20 minutes ago
# 1 week ago
# 5 years ago

format_duration(格式化时长)

format_duration(seconds, hide_days=False)

将给定的以秒(浮点数)为单位的时长值转换为时长格式。

from frappe.utils import format_duration

format_duration(50) # '50s'
format_duration(10000) # '2h 46m 40s'
format_duration(1000000) # '11d 13h 46m 40s'

# Convert days to hours
format_duration(1000000, hide_days=True) # '277h 46m 40s'

comma_and(逗号和)

comma_and(some_list, add_quotes=True)

给定一个列表或元组 some_list,返回格式为 1st item, 2nd item, .... and last item 的字符串。此函数使用 frappe._,因此您不必担心单词 and 的翻译问题。如果 add_quotesFalse,则返回不带引号的项目,否则返回带引号的项目。如果作为参数传递的 some_list 的类型不是列表或元组,则原样返回它(some_list)。

from frappe.utils import comma_and

comma_and([1, 2, 3]) # "'1', '2' and '3'"
comma_and(['Apple', 'Ball', 'Cat'], add_quotes=False) # 'Apple, Ball and Cat'
comma_and('abcd') # 'abcd'

还有一个 comma_or 函数,它与 comma_and 类似,只是分隔符不同,在 comma_or 的情况下,分隔符是 or

money_in_words(金额转大写)

money_in_words(number, main_currency=None, fraction_currency=None)

`number`: A floating point money amount
`main_currency`: Uses this as the main currency. If not given, tries to fetch from default settings or uses `INR` if not found there.

此函数返回带有货币和辅币单位的字符串(金额大写形式)。

from frappe.utils import money_in_words

money_in_words(900) # 'INR Nine Hundred and Fifty Paisa only.'
money_in_words(900.50) # 'INR Nine Hundred and Fifty Paisa only.'
money_in_words(900.50, 'USD') # 'USD Nine Hundred and Fifty Centavo only.'
money_in_words(900.50, 'USD', 'Cents') # 'USD Nine Hundred and Fifty Cents only.'

validate_json_string(验证 JSON 字符串)

validate_json_string(string)

如果给定的 string 是有效的 JSON(JavaScript 对象表示法)字符串,则抛出 frappe.ValidationError。您可以使用 try-except 块来处理对此函数的调用,如下面的代码片段所示。

import frappe
from frappe.utils import validate_json_string

# No Exception thrown
validate_json_string('[]')
validate_json_string('[{}]')
validate_json_string('[{"player": "one", "score": 199}]')

try:
    # Throws frappe.ValidationError
    validate_json_string('invalid json')
except frappe.ValidationError:
    print('Not a valid JSON string')

random_string(随机字符串)

random_string(length)

此函数生成一个包含 length 个字符的随机字符串。这在某些情况下对于生成加密或秘密信息非常有用。

from frappe.utils import random_string

random_string(40) # 'mcrLCrlvkUdkaOe8m5xMI8IwDB8lszwJsWtZFveQ'
random_string(6) # 'htrB4L'
random_string(6) #'HNRirG'

mask_string(掩码字符串)

mask_string(input_string, mask_char="*", show_first=4, show_last=3)

出于隐私保护目的,通过隐藏中间字符来掩码字符串,同时显示可配置的首尾字符以用于识别。

from frappe.utils import mask_string

mask_string("1234567890")  # "1234***890"
mask_string("12345")       # "******"
mask_string("1234567890", mask_char="#", show_first=2, show_last=2)  # "12####90"

unique(去重)

unique(seq)

seq:一个可迭代对象 / 序列

此函数在移除重复项后返回给定序列的元素列表。此外,与 list(set(seq)) 不同,它会保留顺序。

from frappe.utils import unique

unique([1, 2, 3, 1, 1, 1]) # [1, 2, 3]
unique('abcda') # ['a', 'b', 'c', 'd']
unique(('Apple', 'Apple', 'Banana', 'Apple')) # ['Apple', 'Banana']

get_pdf(生成 PDF)

get_pdf(html, options=None, output=None)

`html`: HTML string to render
`options`: An optional `dict` for configuration
`output`: A optional `PdfFileWriter` object.

此函数使用 pdfkitpyPDF2 模块从 HTML 生成 PDF 文件。如果提供了 output,则将生成的页面追加到此对象并返回它,否则返回 PDF 的 byte 流。

例如,生成 PDF 并将其作为对 Web 请求的响应返回:

import frappe
from frappe.utils.pdf import get_pdf

@frappe.whitelist(allow_guest=True)
def generate_invoice():
    cart = [{
        'Samsung Galaxy S20': 10,
        'iPhone 13': 80
    }]

    html = '
<h1>Invoice from Star Electronics e-Store!</h1>'

    # Add items to PDF HTML
    html += '
<ol>'
    for item, qty in cart.items():
        html += f'
<li>{item} - {qty}</li>'
    html += '</ol>'

    # Attaching PDF to response
    frappe.local.response.filename = 'invoice.pdf'
    frappe.local.response.filecontent = get_pdf(html)
    frappe.local.response.type = 'pdf'

get_abbr(获取缩写)

get_abbr(string, max_len=2)

返回给定 string 的缩写(仅首字母)版本,最多包含 max_len 个字母。它广泛用于 Frappe 框架和 ERPNext 中,以生成缩略图或占位图像。

from frappe.utils import get_abbr

get_abbr('Gavin') # 'G'
get_abbr('Coca Cola Company') # 'CC'
get_abbr('Mohammad Hussain Nagaria', max_len=3) # 'MHN'

validate_url(验证 URL)

validate_url(txt, throw=False, valid_schemes=None)

`txt`: A string to check validity
`throw`: Weather to throw an exception if `txt` does not represent a valid URL, `False` by default
`valid_schemes`: A string or an iterable (list, tuple or set). If provided, checks the given URL's scheme against this.

此实用函数可用于检查字符串是否表示有效的 URL 地址。

from frappe.utils import validate_url

validate_url('google') # False
validate_url('https://google.com') # True
validate_url('https://google.com', throw=True) # throws ValidationError

validate_email_address(验证电子邮件地址)

validate_email_address(email_str, throw=False)

返回一个字符串,其中包含给定 email_str 中存在的电子邮件地址或以逗号分隔的有效电子邮件地址列表。如果 throwTrue,则在给定字符串中不存在有效电子邮件地址时抛出 frappe.InvalidEmailAddressError,否则返回空字符串。

from frappe.utils import validate_email_address

# Single valid email address
validate_email_address('[email protected]') # '[email protected]'
validate_email_address('other text, [email protected], some other text') # '[email protected]'

# Multiple valid email address
validate_email_address(
    'some text, [email protected], some other text, [email protected], yet another no-emailic phrase.'
) # '[email protected], [email protected]'

# Invalid email address
validate_email_address('some other text') # ''

validate_phone_number(验证电话号码)

validate_phone_number(phone_number, throw=False)

如果 phone_number(字符串)是有效的电话号码,则返回 True。如果 phone_number 无效且 throwTrue,则会抛出 frappe.InvalidPhoneNumberError

from frappe.utils import validate_phone_number

# Valid phone numbers
validate_phone_number('753858375') # True
validate_phone_number('+91-75385837') # True

# Invalid phone numbers
validate_phone_number('invalid') # False
validate_phone_number('87345%%', throw=True) # InvalidPhoneNumberError

frappe.cache()

cache()

返回 Redis 连接,该连接是 RedisWrapper 类的实例,继承自 redis.Redis 类。您可以使用此连接来利用 Redis 缓存存储/检索键值对。

import frappe

cache = frappe.cache()

cache.set('name', 'frappe') # True
cache.get('name') # b'frappe'

frappe.sendmail()

sendmail(recipients=[], sender="", subject="No Subject", message="No Message", as_markdown=False, template=None, args=None, **kwargs)

`recipients`: List of recipients
`sender`: Email sender. Default is current user or default outgoing account
`subject`: Email Subject
`message`: (or `content`) Email Content
`as_markdown`: Convert content markdown to HTML
`template`: Name of html template (jinja) from templates/emails folder
`args`: Arguments for rendering the template

在大多数情况下,上述参数已经足够,但此函数还可以接受许多其他关键字参数。要查看所有关键字参数,请查看此函数的实现(frappe/__init__.py)。

此函数可以使用用户的默认电子邮件账户或全局默认电子邮件账户发送邮件。

import frappe

recipients = [
    '[email protected]',
    '[email protected]'
]

frappe.sendmail(
    recipients=recipients,
    subject=frappe._('Birthday Reminder'),
    template='birthday_reminder',
    args=dict(
        reminder_text=reminder_text,
        birthday_persons=birthday_persons,
        message=message,
    ),
    header=_('Birthday Reminder 🎂')
)

示例 Jinja 模板文件:

<!-- templates/emails/birthday_reminder.html -->
<div>
<div class="gray-container text-center">
<div>
 {% for person in birthday_persons %}
 {% if person.image %}
 <img src="%7B%7B%20person.image%20%7D%7D" title="{{ person.name }}">
 {% endif %}
 {% endfor %}
 </div>
<div style="margin-top: 15px;"><span>{{ reminder_text }}</span>
<p class="text-muted">{{ message }}</p>
</div>
</div>

<h3 id="attaching-files">附加文件</h3>
<p>您可以通过向 <code>sendmail 函数传递附件列表来轻松地将文件附加到邮件中:

frappe.sendmail(
    ["[email protected]", "[email protected]"],
    message="## hello, *bro*"
    attachments=[{"file_url": "/files/hello.png"}],
    as_markdown=True
)

请注意,附件是一个字典列表,其中包含键 file_url。您可以在 File 文档的 file_url 字段中找到此 file_url

文件锁

文件锁可用于同步进程,以避免竞争条件。

示例:如果多个写入者尝试写入同一个文件,可能会导致竞争条件。因此,我们创建一个命名锁,以便进程可以看到该锁并等待其可用于写入。

from frappe.utils.synchronization import filelock

def update_important_config(config, file):
    with filelock("config_name"):
        json.dumps(config, file)

get_filtered_list_url

get_filtered_list_url(doctype, docnames=None)

当创建了多个文档,并且您希望在列表视图中向用户展示所有这些文档时,此功能非常有用。

from frappe.utils import get_filtered_list_url

get_filtered_list_url("Work Order", [
    "MFG-WO-2025-00027",
    "MFG-WO-2025-00028",
    "MFG-WO-2025-00029"
])
# → 'http://
<site>/app/work-order?name=["in",["MFG-WO-2025-00027","MFG-WO-2025-00028","MFG-WO-2025-00029"]]'
</site>

get_filtered_list_link(doctype, docnames=None, label=None)

当创建了多个文档,并且您希望显示一个简洁的链接而不是冗长的消息对话框链接时,此功能非常有用。

from frappe.utils import get_filtered_list_link

get_filtered_list_link("Work Order", [
    "MFG-WO-2025-00027",
    "MFG-WO-2025-00028",
])
# → '<a href="http://<site>/app/work-order?name=%5B...%5D">Work Order</a>'

语言解析

在这里,让我们来看看 Frappe 中语言是如何解析的,以及你如何在你的
Frappe 应用或脚本中使用它们。

你会话的语言取决于 frappe.lang 的值。它的解析顺序如下:

  1. 表单字典 > _lang
  2. Cookie > preferred_language [仅限访客用户]
  3. 请求头 > Accept-Language [仅限访客用户]
  4. 用户文档 > language
  5. 系统设置 > language

表单字典:_lang

表单字典的 _lang 参数具有最高优先级。设置此参数将
更新给定请求中的所有可翻译组件。Frappe 在某些地方使用这种机制
来处理邮件模板和打印视图。

虽然在每个请求中传递 ?_lang=ru 可能不太实际。如果你
想要持久但临时的语言设置,你可以在 cookies 中设置
preferred_language 键。Frappe 利用这一点来实现网站
语言切换器。此方法可用于根据客户端来持久化语言设置。

仅对访客用户生效。对已登录用户忽略。

请求头:Accept-Language

另一种相对更简洁、更标准的管理语言的方法是使用
Accept-Language 请求头。如果前两种方法都未设置,Frappe 会开始
解析此请求头的值,其中包含客户端可接受语言的有序集合。你可以查看 Mozilla 文档
以获取更多相关信息。

仅对访客用户生效。对已登录用户忽略。

用户与系统设置

用户文档中有一个 language 字段,用于设置该用户的会话语言。
此设置会跨设备、客户端持久保存。这允许特定
用户以他们选择的语言查看网站和 Desk。例如,
如果一个用户在“法语”网站上将其语言设置为“俄语”,当他们
登录时,网站会自动翻译成俄语。

系统设置中的 language 字段用于设置整个
网站的语言。它的优先级最低,是所有会话的备用语言。

请求生命周期

Web 应用的用户可以访问不同的 URL,例如 /about/posts/api/resources。每个请求都根据以下请求类型进行处理。

  1. /api 开头的 API 请求由 REST API 处理器处理。
  2. 文件下载,如备份文件 (/backups)、公共文件 (/files) 和私有文件 (/private/files),会单独处理,以响应可下载的文件。
  3. 网页请求,如 /about/posts,由网站路由器处理。本页将进一步说明。

详细了解 API 请求和静态文件。

请求预处理

在触发路由规则之前,会发生一些操作。这些操作包括预处理请求、初始化记录器和速率限制器。

路径解析器

一旦请求从 app.py 到达网站路由器,它就会通过路径解析器进行处理。

路径解析器执行以下操作:

重定向解析

路径解析器尝试为传入的请求路径解析任何可能的重定向。路径解析器从 website_redirects 钩子获取重定向规则,并从网站设置中获取路由重定向。

路由解析

如果传入的请求路径没有重定向,路径解析器会尝试解析路由,以根据 website_routing_rules 钩子的规则以及启用了 has_web_view 的 DocType 文档中设置的动态路由来获取最终端点。

渲染器选择

一旦获得最终端点,它会传递给所有可用的页面渲染器,以检查哪个页面渲染器可以渲染给定的路径。第一个对 can_render 请求返回 True 的页面渲染器将用于渲染该路径。

页面渲染器

页面渲染器负责为给定的端点渲染页面或响应页面。页面渲染器使用 Python 类实现。一个页面渲染器类需要有两个方法,即 can_renderrender

路径解析器调用 can_render 来检查某个渲染器实例是否可以渲染特定路径。
一旦某个渲染器从 can_render 返回 True,该渲染器类就负责渲染该路径。

页面渲染器类示例

from frappe.website.page_renderers.base_renderer import BaseRenderer

class PageRenderer(BaseRenderer):
 def can_render(self):
 return True

 def render(self):
 response_html = "Response"
 return self.build_response(response_html)

以下是处理所有通用类型网页的标准页面渲染器。

StaticPage

使用 StaticPage,您可以从站点上安装的任何应用的 www 文件夹中提供 PDF、图片等文件。任何是以下类型 htmlmdjsxmlcsstxtpy 的文件都被视为静态文件。
提供静态文件的首选方式是将它们添加到您的 frappe 应用的 public 文件夹中。这样,它们将由 NGINX 直接提供,利用压缩和缓存,同时减少延迟。

TemplatePage

TemplatePage 在所有应用中查找 www 文件夹,如果它是 HTML 或 Markdown 文件,则返回该文件;如果它是一个文件夹,则返回该文件夹中的 index.htmlindex.md 文件。

WebformPage

如果请求路径与任何可用的 Web 表单路由匹配,WebformPage 会尝试在 Web 表单列表中渲染 Web 表单。

DocumentPage

如果 DocType 的 /templates 文件夹中有可用的文档模板,DocumentPage 会尝试渲染该模板。模板文件名应与 DocType 名称相同。例如:如果您想为 User 文档类型添加文档模板,则 User DocType 的 templates 文件夹中应有 user.html。文件夹结构将类似于 doctype/user/templates/user.html

ListPage

如果 DocType 的 /templates 文件夹中有列表模板,ListPage 将渲染它。请查看 Blog Post 模板文件夹以获取实现参考。

PrintPage

PrintPage 渲染文档的打印视图。它使用标准打印格式,除非通过 default_print_format 为 DocType 设置了不同的打印格式。

NotFoundPage

NotFoundPage 渲染标准的未找到页面,并响应 404 状态码。

NotPermittedPage

NotPermittedPage 渲染标准的权限拒绝页面,并带有 403 状态码。

添加自定义页面渲染器

如果您有其他标准页面渲染器未处理的需求,可以通过 page_renderer [钩子] 添加自定义页面渲染器。

# in hooks.py of your custom app

page_renderer = "path.to.your.custom_page_renderer.CustomPage"

页面渲染器类需要有两个方法,即 can_renderrender

路径解析器调用 can_render 来检查某个渲染器实例是否可以渲染特定路径。
一旦某个渲染器从 can_render 返回 True,该渲染器类就负责渲染该路径。

注意: 自定义页面渲染器具有优先权,其 can_render 方法将在标准页面渲染器之前被调用。

示例:


from frappe.website.utils import build_response
from frappe.website.page_renderers.base_renderer import BaseRenderer

class CustomPage(BaseRenderer):
 def can_render(self):
 return True

 def render(self):
 response_html = "Custom Response"
 return self.build_response(response_html)

注意:您还可以扩展标准页面渲染器,以覆盖或使用某些标准功能。

金贾API

这些是 frappe 提供的白名单方法,可用于 Jinja
模板中。

frappe.format

frappe.format(value, df, doc)

将原始值(存储在数据库中)格式化为用户可读的格式。
例如,将 2019-09-08 转换为 08-09-2019

用法

{{ frappe.format('2019-09-08', {'fieldtype': 'Date'}) }}

09-08-2019

frappe.format_date

frappe.format_date(date_string)

将日期格式化为人类可读的长格式。

用法

{{ frappe.format_date('2019-09-08') }}

September 8, 2019

frappe.get_url

frappe.get_url()

返回站点 URL

用法

{{ frappe.get_url() }}

https://frappe.io

frappe.get_doc

frappe.get_doc(doctype, name)

根据名称返回文档。

用法


 {% set doc = frappe.get_doc('Task', 'TASK00002') %}
 {{ doc.title }} - {{ doc.status }}

 Buy Eggs - Open

frappe.get_all

frappe.get_all(doctype, filters, fields, order_by, start, page_length, pluck)

返回某个 DocType 的所有记录列表。如果未提供 name 参数,则仅返回文档的 fields

签名

frappe.get_all(doctype, filters, fields, order_by, start, page_length)

用法


 {% set tasks = frappe.get_all('Task', filters={'status': 'Open'}, fields=['title', 'due_date'], order_by='due_date asc') %}
 {% for task in tasks %}

### {{ task.title }}

Due Date: {{ frappe.format_date(task.due_date) }}

 {% endfor %}

### Redesign Website

Due Date: September 8, 2019

### Add meta tags on websites

Due Date: September 22, 2019

frappe.get_list

frappe.get_list(doctype, filters, fields, order_by, start, page_length)

frappe.get_all 类似,但会根据当前会话用户的权限过滤记录。

frappe.db.get_value

frappe.db.get_value(doctype, name, fieldname)

从文档中返回单个字段值(或值列表)。

用法



 {% set company_abbreviation = frappe.db.get_value('Company', 'TennisMart', 'abbr') %}
 {{ company_abbreviation }}

 {% set title, description = frappe.db.get_value('Task', 'TASK00002', ['title', 'description']) %}
 ### {{ title }}

{{ description }}

TM

frappe.db.get_single_value

frappe.db.get_single_value(doctype, fieldname)

从单个 DocType 返回字段值。

用法



 {% set timezone = frappe.db.get_single_value('System Settings', 'time_zone') %}
 {{ timezone }}

 Asia/Kolkata

frappe.get_system_settings

frappe.get_system_settings(fieldname)

从系统设置中返回字段值。

用法


 {% if frappe.get_system_settings('country') == 'India' %}
 Pay via Razorpay
 {% else %}
 Pay via PayPal
 {% endif %}

Pay via Razorpay

frappe.get_meta

frappe.get_meta(doctype)

返回文档类型的元数据。其中包含字段、标题字段、
图片字段等信息。

用法



 {% set meta = frappe.get_meta('Task') %}
 Task has {{ meta.fields | len }} fields.
 {% if meta.get_field('status') %}
 It also has a Status field.
 {% endif %}

 Task has 18 fields. It also has a Status field.

frappe.get_fullname

frappe.get_fullname(user_email)

返回传入用户邮箱的完整姓名。如果未传入用户,则假定为
当前登录用户。

用法


The fullname of [email protected] is {{ frappe.get_fullname('[email protected]') }}
The current logged in user is {{ frappe.get_fullname() }}

The fullname of [email protected] is Faris Ansari
The current logged in user is John Doe

frappe.render_template

frappe.render_template(template_name, context)

使用上下文渲染 Jinja 模板字符串或文件。

用法



 {{ frappe.render_template('templates/includes/footer/footer.html', {}) }}

{{ frappe.render_template('{{ foo }}', {'foo': 'bar'}) }}

bar

frappe._

frappe._(string)_(string)

用法


{{ _('This string should get translated') }}

इस तार का अनुवाद होना चाहिए

frappe.session.user

返回当前会话用户

frappe.session.csrf_token

返回当前会话的 CSRF 令牌

frappe.form_dict

如果模板在 Web 请求中求值,则 frappe.form_dict
查询参数的字典,否则为 None

frappe.lang

翻译函数当前使用的语言。两位小写字母代码。