钩子

钩子(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 文件钩子