控制器

控制器(Controller)是一个普通的 Python 类,它继承自 frappe.model.Document 基类。该基类是 DocType 的核心逻辑,负责处理值如何从数据库加载、如何解析以及如何保存回数据库。

当你创建一个名为 Person 的 DocType 时,系统会生成一个名为 person.py 的 Python 文件,其内容如下:

import frappe
from frappe.model.document import Document

class Person(Document):
    pass

所有字段都可以作为类的属性来访问。

控制器方法

你可以向控制器添加自定义方法,并通过 doc 对象来调用它们。例如:

# controller class
class Person(Document):
    def get_full_name(self):
        """Returns the person's full name"""
        return f"{self.first_name} {self.last_name}"

# somewhere in your code
>>> doc = frappe.get_doc("Person", "000001")
>>> doc.get_full_name()
John Doe

控制器钩子

为了在文档的生命周期中添加自定义行为,我们提供了控制器钩子。

方法名称 描述 插入 保存 提交 取消 提交后更新
before_insert 在文档准备插入之前调用。 X
before_naming 在设置文档的 name 属性之前调用。 X
autoname 如果在控制器中定义,此方法用于设置文档的 name 属性。 X
before_validate 此钩子在验证之前调用,用于自动设置缺失的值。 X X X
validate 使用此方法抛出任何验证错误并阻止文档保存。 X X X
before_save 此方法在文档保存之前调用。 X X
before_submit 此方法在文档提交之前调用。 X X
before_cancel 此方法在文档取消之前调用。 X
before_update_after_submit 当已提交文档的字段被更新时调用此方法。 X
db_insert 此方法将文档插入数据库,除非你在处理虚拟 DocType,否则不要覆盖此方法。 X
after_insert 在文档插入数据库之后调用。 X
db_update 此方法更新数据库中的文档,除非你在处理虚拟 DocType,否则不要覆盖此方法。 X X X X
on_update 当现有文档的值被更新时调用。 X X X
on_submit 当文档被提交时调用。 X
on_cancel 当已提交的文档被取消时调用。 X
on_update_after_submit 当已提交文档的值被更新时调用。 X
on_change 当文档的值被更改时调用。此方法也会在 db_set 执行时被调用,因此在此方法中执行的操作应该是幂等的。 X X X X X

除了针对典型操作的文档事件外,你还可以挂钩到其他操作。

方法名称 描述
before_rename 在文档重命名之前调用。
after_rename 在文档重命名之后调用。
on_trash 当文档被删除时调用。
after_delete 在文档被删除之后调用。

要使用控制器钩子,只需定义一个具有该名称的类方法。例如:

class Person(Document):
    def validate(self):
        if self.age <= 18:
            frappe.throw("Person's age must be at least 18")

    def after_insert(self):
        frappe.sendmail(recipients=[self.email], message="Thank you for registering!")

如果钩子不能满足你的需求,你也可以覆盖预定义的文档方法以添加你自己的行为。例如,要覆盖 save() 方法:

class Person(Document):
    def save(self, *args, **kwargs):
        super().save(*args, **kwargs) # call the base save method
        do_something() # eg: trigger an API call or a Rotating File Logger that "User X has tried updating this particular record"

doc 对象默认提供了许多方法。你可以在这里找到完整的列表。

1. 创建文档

要创建新文档并将其保存到数据库:

doc = frappe.get_doc({
    'doctype': 'Person',
    'first_name': 'John',
    'last_name': 'Doe'
})
doc.insert()

doc.name # 000001

2. 加载文档

要从数据库获取现有文档:

doc = frappe.get_doc('Person', '000001')

# doctype fields
doc.first_name # John
doc.last_name # Doe

# standard fields
doc.creation # datetime.datetime(2018, 9, 20, 12, 39, 34, 236801)
doc.owner # [email protected]

文档(Document)

文档是 DocType 的一个实例。它通常映射到数据库表中的一行。在代码中我们称之为 doc

示例

假设我们有一个名为 ToDo 的 DocType,包含以下字段:

  • description
  • status
  • priority

现在,如果我们想从数据库中查询文档,可以使用 ORM。

>>> doc = frappe.get_doc("ToDo", "0000001")
<todo:>

>>> doc.as_dict()
{'name': '0000001',
 'owner': 'Administrator',
 'creation': datetime.datetime(2022, 3, 28, 18, 20, 23, 275229),
 'modified': datetime.datetime(2022, 3, 28, 18, 20, 23, 275229),
 'modified_by': 'Administrator',
 'docstatus': 0,
 'idx': 0,
 'status': 'Open',
 'priority': 'Medium',
 'color': None,
 'date': None,
 'allocated_to': None,
 'description': 'Test',
 'reference_type': None,
 'reference_name': None,
 'role': None,
 'assigned_by': 'Administrator',
 'assigned_by_full_name': 'Administrator',
 'sender': None,
 'assignment_rule': None,
 'doctype': 'ToDo'}
</todo:>

你可以获取 descriptionstatuspriority 的值,同时也会得到像 creationownermodified_by 这样的字段,这些字段是框架默认在所有 docs 上添加的。

类型注解

在版本 15 中引入。

Frappe 支持在控制器文件中自动生成 Python 类型注解。这些注解可用于控制器文件中的自动补全、参考和类型检查。

class Person(Document):
    # begin: auto-generated types
    # This code is auto-generated. Do not modify anything in this block.

    from typing import TYPE_CHECKING

    if TYPE_CHECKING:
        from frappe.types import DF

        first_name: DF.Data
        last_name: DF.Data 
        user: DF.Link
    # end: auto-generated types
    pass

注意:这些注解在创建或更新文档类型时生成。如果你修改了代码块,它将在下次更新时被覆盖。

你可以通过添加以下钩子来配置应用中的自动导出。

# hooks.py

export_python_type_annotations = True

了解更多关于类型注解的信息:

  • https://docs.python.org/3/library/typing.html
  • VS Code 用户可以安装 Python 扩展以获得更好的自动补全功能 – https://code.visualstudio.com/docs/languages/python
  • 大多数其他编辑器都有使用 LSP 的等效插件系统。

单据字段

DocField 定义了 DocType 的一个属性(或字段)。你可以为 DocField 定义列名、标签、数据类型等。例如,一个待办事项(ToDo)文档类型具有字段 descriptionstatuspriority。这些字段最终会成为数据库表 tabToDo 中的列。

示例

DocField 存储有关该字段的元数据。下面描述了其中的一些。

[
 {
 "label": "Description", // the value shown to the user (Form, Print, etc)
 "fieldname": "description", // the property name we refer in code, also the column name
 "fieldtype": "Text Editor", // the fieldtype which also decides how to store this value
 "reqd": 1 // whether this field is mandatory
 },
 {
 "label": "Status",
 "fieldname": "status",
 "fieldtype": "Select",
 "options": [
 "Open",
 "Pending",
 "Closed"
 ]
 },
 {
 "label": "Priority",
 "fieldname": "priority",
 "fieldtype": "Select",
 "options": [ // list of options for select
 "Low",
 "Medium",
 "High"
 ],
 "default": "Low" // the default value to be set
 },
 {
 "label": "Completed By",
 "fieldname": "completed_by",
 "fieldtype": "Link",
 "options": "User",
 "depends_on": "eval: doc.status == 'Closed'", // the condition on which this field's display depends
 },
 {
 "collapsible": 1,
 "collapsible_depends_on": "eval:doc.status!='Closed'", // determines if a Section Break field is collapsible
 "fieldname": "sb_details",
 "fieldtype": "Section Break",
 "label": "Details"
 },
 {
 "fieldname": "amount",
 "fieldtype": "Currency", // Currency field
 "label": "Amount",
 "non_negative": 1, // determines whether this field value can be negative
 "options": "INR",
 }
]

类似于 depends_on 属性(该属性决定字段是否显示),
在版本 12 中,我们引入了两个新属性:

  • mandatory_depends_on:如果满足此条件,该字段将成为必填项。
  • read_only_depends_on:如果满足此条件,该字段将变为只读。

Frappe 内置了超过 30 种不同的字段类型。
这些字段类型适用于各种使用场景。你可以在下一页了解更多关于字段类型的信息。

命名

Frappe 中的所有 DocType 都有一个名为 name 的主键。这是您查找记录并使用 ORM 操作记录的唯一 ID。

命名方法

您可以配置创建新文档时如何命名。框架中提供了 9 种命名方式:

  1. 由用户设置
  2. 自动递增
  3. 按字段名
  4. 按“命名系列”字段
  5. 表达式
  6. 表达式(旧式)
  7. 随机
  8. UUID
  9. 通过脚本

1. 由用户设置

文档名称由用户在创建时手动输入。

2. 自动递增

系统通过递增最后创建的文档来生成一个顺序数字名称。

关键注意事项:

  • 编号从 1 开始。
  • 不保证连续无间隙: 已删除的记录编号不会被重用。例如,如果您有名为 1234 的文档,并且您删除了 3,则下一个文档将被命名为 5,而不是 3
  • 一旦设置了此命名方式,除非 DocType 中没有文档,否则无法切换到其他命名方案。
  • 除非 DocType 中没有文档,否则无法从其他命名方案切换到此命名方案。


自动递增

3. 按字段名

文档名称直接从特定字段的值中获取。

注意: 所选字段中的值必须始终是唯一的。


按字段名

4. 按“命名系列”字段

命名模式来源于文档中的特定字段(通常是 naming_series)。

例如,如果您的文档中有一个字段 naming_series,并且其值设置为 PRE.#####,则将使用该模式来生成名称(例如,PRE00001)。此值可以根据文档而变化,允许同一 DocType 中的不同文档遵循不同的模式。

要求: 这仅在您的 DocType 中有一个名为 naming_series 的字段时才有效。


按“命名系列”

5. 表达式

您可以提供一个标准的命名模式,该模式将自动递增。

例如,如果您将模式设置为 PRE-.#####

  • 创建的第一个文档将被命名为 PRE-00001
  • 第二个将是 PRE-00002
  • 依此类推…

    表达式

您可以使用此方法,通过多个字段值和变量灵活地配置命名方案。各个部分由点号 . 分隔。

示例模式:

EXAMPLE-.MM.-test-.{fieldname1}.-.{fieldname2}.-.#####

此格式允许您组合:

  • 静态文本(EXAMPLEtest
  • 日期变量(MMYYYYDDWWtimestamp
  • 字段值({fieldname1}
  • 自动递增数字( #####

6. 表达式(旧式)

警告:在 v16 中弃用 此方法已弃用,并将在版本 16 中移除。

这是一种高度灵活的方法,可以使用多个字段值和变量配置命名方案。您可以直接在 DocType 设置中的 自动名称 字段中输入此模式。

示例模式:

EXAMPLE-{MM}-test-{fieldname1}-{fieldname2}-{#####}

此格式允许您组合:

  • 静态文本 EXAMPLEtest
  • 日期变量 {MM}{YYYY}{DD}
  • 字段值 {fieldname1}
  • 自动递增数字 {#####}

7. 随机

生成一个随机的字母数字字符串作为文档名称。


随机

8. UUID

使用随机生成的通用唯一标识符(UUID v4)作为文档名称。

示例: 550e8400-e29b-41d4-a716-446655440000


UUID

9. 通过脚本

您可以使用 DocType 的 Python 文件中的 autoname 控制器方法定义自定义命名逻辑。这使您可以完全控制命名过程。

from frappe.model.naming import getseries

class Project(Document):
    def autoname(self):
    # select a project name based on customer
    prefix = f"P-{self.customer}"
    series = getseries(prefix, 3)
    self.name = f"{prefix}-{series}"

按文档命名规则

您还可以通过创建 文档命名规则 来为 DocType 创建命名规则。

您可以为特定 doctype 创建多个文档命名规则,这些规则可以根据筛选条件有选择地应用。

要定义文档命名规则,您必须指定:

  1. 应用该规则的文档类型
  2. 规则的优先级(优先级高的规则将首先应用)
  3. 应用规则的条件
  4. 命名规则

编号

您可以根据定义的条件为规则定义各种编号前缀。这是通过设置该规则的前缀和位数来完成的。

例如,如果您要为高优先级待办事项创建单独的编号:

  1. 前缀:todo-high-
  2. 位数:3

将产生类似 todo-high-001todo-high-002 等的编号。

命名优先级

当多个命名规则可能适用时,框架按以下顺序确定优先级:

  1. 文档命名规则:在 DocType 的 autoname 属性中定义。
  2. 控制器方法:在控制器脚本的 autoname 方法中定义的逻辑(如果已定义,则覆盖 DocType 设置)。

特殊规则

  1. 子 DocType 不遵循命名规则
  2. 修订后的文档会在原文档名称后添加后缀(如 -1-2 等)

字段类型

Frappe 框架中提供了多种字段类型。每种字段类型都有其特定的用途,可用于在文档中输入和存储不同类型的数据。字段类型用于在桌面端和 Web 表单中渲染组件。

数据

数据字段是一个简单的文本字段。它允许您输入最多 140 个字符的值,是最通用的字段类型。

您可以为以下数据类型启用验证:

  1. 姓名
  2. 电子邮件
  3. 电话
  4. 网址

只需将选项分别设置为“姓名”、“电子邮件”、“电话”或“网址”即可。

将选项设置为“IBAN”可启用每 4 个字符一组的格式化显示。

链接字段连接到另一个主数据,并从中获取数据。例如,在报价单主数据中,客户是一个链接字段。要了解更多信息,请点击此处。

动态链接字段可以搜索并保存任何文档/文档类型的值。点击此处了解动态链接字段的工作原理。

复选框

这将使您能够在此处拥有一个复选框。您可以将 Default 值设置为 1,默认情况下它将被勾选。

选择

使用“选择”字段类型,您可以创建一个下拉字段。您可以在选项字段中指定所有可选值,每个值用换行符分隔。

可以将其中一个可选值复制到默认值字段中。这样,在新表单中该值将默认被选中。

如果您启用选项排序复选框,则值将按字母顺序显示(以用户的语言显示)。

以下截图显示了如何在文档类型中或通过自定义表单定义“选择”字段:

这是它在新表单中的渲染效果:

表格

使用“表格”字段,您可以将另一个文档类型作为子表渲染在您的表单中。

首先,您需要选择或定义一个启用了是子表复选框的文档类型。然后,您可以添加一个类型为“表格”的字段,并将子表文档类型的名称粘贴到选项字段中。

例如,ERPNext 定义了一个名为“采购收货项目”的子文档类型,并启用了是子表选项:

另一个名为“采购收货”的文档类型有一个类型为“表格”的字段,其选项设置为上述子文档类型“采购收货项目”:

最后,子表将渲染在采购收货表单中:

附件

附件字段允许您从文件管理器中浏览文件并将其附加到此字段中。

附件画廊字段以响应式画廊的形式显示附加到当前文档的文件。图片显示为缩略图,可以打开以进行更大尺寸的预览。其他文件类型显示为文件卡片,并在新标签页中打开。

具有写入权限的用户可以直接从画廊上传和删除附件。启用只读将隐藏这些操作。必须先保存文档,然后才能上传文件。

默认情况下,画廊显示属于该文档的所有附件。使用过滤器来限制显示的文件记录。例如,仅显示 PDF 文件:

[["File", "file_type", "=", "PDF"]]

要仅显示通过特定画廊上传的文件,请使用该画廊的字段名称按 attached_to_field 进行过滤:

[["File", "attached_to_field", "=", "marketing_assets"]]

如果通过画廊上传的文件默认应为公开,请启用使附件公开(默认)选项。

附件图片

附件图片是一种字段,允许您附加 jpeg、png 等格式的图片。该图片将成为代表该特定文档类型的图像。例如,如果您希望在项目文档类型中显示项目的图片,您可以将字段设置为附件图片字段。

文本编辑器

文本编辑器是一个文本字段,渲染一个所见即所得的编辑器用于输入。它具有多种文本格式化选项。

日期

此字段允许您在此字段中输入日期。

日期和时间

此字段将为您提供一个日期和时间选择器。默认设置为当前日期和时间(由您的计算机提供)。

条形码

在此字段中,您可以将字段指定为条形码,这将允许您输入条形码编号。完成后,系统将自动根据该编号生成条形码。

按钮

此字段用于在文档中放置一个按钮。它可用于执行特定操作,如发布博客文章、触发某个动作等。

代码

此字段类型可用于接收 code 作为输入。文档表单中会渲染一个代码编辑器。可选地,您可以在字段类型选项中提供一种语言以启用语法高亮。例如,下面是一个 Code 类型的字段,其选项设置为 Python

您可以通过设置“选项”来为以下语言启用基本的语法验证。

  1. Python(用于脚本)
  2. PythonExpression(用于必须求值为某个值的简单单行表达式)例如:分配规则条件

PythonPythonExpression 之间的差异示例:

  • variable = 42 是有效的 Python 代码,但不是有效的 PythonExpression,因为该赋值不会求值为任何值。
  • variable == 42 既是有效的 Python 代码,也是有效的 PythonExpression,因为该表达式可以求值为某个值。

颜色

这将允许用户通过渲染的颜色选择器输入颜色,或直接输入十六进制颜色代码。

分栏符

这是一个 'meta' 字段类型,它不存储任何输入数据,但可用于在文档视图或表单中指示分栏。

例如,

将显示为:

货币

货币字段保存数值,如项目价格、金额等。货币字段的值最多可包含六位小数。此外,您还可以为货币字段显示货币符号。

浮点数

浮点数字段携带数值,最多可包含九位小数。

地理位置

地理位置字段将显示一个地图视图,您可以在其上绘制多边形、线条和点。数据以 GeoJSON feature_collection 格式存储。

如果您添加一个字段名恰好为“location”的地理位置字段,您将获得一个额外的地图视图(相当于列表视图,但在地图上显示)。顺便说一下,如果您创建两个分别名为“latitude”和“longitude”的字段,这也同样有效。

注意:Frappe 使用“Leaflet”库,该库将坐标存储为 [纬度, 经度],这与 GeoJSON 通常的做法相反。

HTML

这会将 Options 中输入的内容作为 HTML 渲染在文档表单或视图页面中。以下是一个示例:

将显示为:

图像

图像字段将渲染在另一个附件字段中选择的图像文件。

对于图像字段,应在“选项”(在 DocType 中)中提供一个字段名称,该字段中附加了图像文件。通过引用该字段中的值,图像将作为图像字段中的引用。

将显示为:

整数

整数字段保存数值,没有小数位。

小文本

小文本字段携带文本内容,其字符限制比数据字段更大。

长文本

当您需要输入无字符限制的数据时,可以将字段定义为长文本字段。

文本

此字段类型允许您在字段中添加文本。小文本、长文本和文本字段的字符限制将根据关系数据库管理系统来确定。

Markdown 编辑器

此字段允许您以 Markdown 格式添加文本。此字段类型还提供渲染后 HTML 的 Preview 视图:

当点击预览时:

密码

密码字段将包含解码后的值。此类型的字段可用于存储敏感数据,如密码、口令、密钥等。

百分比

您可以将字段定义为百分比字段,在后台将按百分比进行计算。

评分

此字段可用于显示交互式星级评分输入。默认显示的星数为 5,但是,您可以通过在该特定评分字段的选项字段中输入 3 到 10 之间的数字来轻松更改此设置。

您还可以提供半星评分,例如 3.5 分(满分 5 分)。

只读

只读字段将携带从另一个表单获取的数据,这些数据不可编辑。如果其值的来源是预先确定的,您应将字段类型设置为只读。

分节符

分节符用于将表单划分为多个部分。任何跟在分节符字段之后(并且在任何其他 Section Break 之前)的字段都将属于这个新部分。

分页符

分页符用于将表单划分为多个选项卡。任何跟在分页符之后直到下一个 Tab Break 之前的字段都将属于这个新选项卡。

注意: 如果 DocType 的 fields 表不是以“页签分隔”开头的,系统将使用一个名为 Details 的默认页签分隔。这种情况仅在 DocType 的 fields 表中至少有一个 Tab Break 时才会发生。

签名

您可以将字段定义为签名字段,在此字段中添加数字签名。阅读签名字段的文档以了解更多信息。

表格多选

这是“链接”类型和“表格”类型字段的组合。与带有“添加行”按钮的子表不同,在一个字段中可以同时选择多个值。

时间

这是一个时间字段,您可以在该字段中定义时间。

时长

如果您想定义一个时间段,可以使用时长字段。

如果您不想以天或秒为单位跟踪时长,可以在表单中分别启用“隐藏天数”和“隐藏秒数”选项。一天等于24小时。

JSON

这将在您的数据库中创建一个 JSON 类型的列,并为桌面控件添加语法高亮功能。

虚拟单据字段

虚拟文档字段(Virtual DocField)是给定文档(或记录)的一个动态属性。它是一个计算属性,不存储在站点数据库中。这可用于表示可能是其他静态文档属性函数的数值。


人员表单

一个人的年龄是其出生日期的函数,也就是说,如果你知道一个人的出生日期,就可以算出他的年龄。年龄也是一个连续值;它可能每年、每月、每天甚至每小时都在变化,具体取决于你想要的粒度。另一个属性是人的姓名。最常见的实现会有名字、中间名和姓氏,而在视图中,它们会组合在一起显示,如”Jon Raphael Doe”。虽然当字符串可以轻松拼接时,将全名保存为单独属性可能意义不大,但这些都是虚拟文档字段更合理的几个例子。


人员文档类型

在这里,我们向人员文档类型添加了三个字段:两个用于存储名字和姓氏(存储在站点数据库中),另一个利用这些数据来填充第三个字段”全名”。在此示例中,选项字段接收相应虚拟字段的返回值作为输入。


人员文档类型 – 文档字段

到目前为止,我们讨论了依赖于系统中属性的字段的可能性。但这可以轻松扩展到不仅仅依赖于你的文档类型数据的情况。你可能还想获取多个外部服务的状态,或者任何其他可以在此映射的内容。

如何使用虚拟文档字段

实现此功能涉及的步骤如下:

1. 定义虚拟文档字段

定义虚拟文档字段相当简单。只需在文档字段的配置中勾选”虚拟”复选框即可。虚拟文档字段不会在文档类型的表中创建相应的列。这使得该字段在表单视图中为”只读”。

注意:除非你明确知道自己在做什么,否则避免将现有文档字段设为虚拟字段。

2. 为字段定义数据源

第一步只是为值添加了一个占位符。如果没有添加一些代码来指定字段应显示什么,字段本身就不存在。有两种方法可以实现这一点:

  • 通过扩展文档类型控制器

添加一个与虚拟字段同名的 Python 属性即可实现。这是最灵活的方法;你可以串联内部 API 请求,或从多个数据源获取数据,可能性是无限的。

class Person(Document):
 @property
 def age(self):
 return frappe.utils.now_datetime() - self.creation
  • 使用 DocField.options

这种方法限制稍多,因为它允许你直接从桌面端编写代码。服务器脚本中允许的实用程序和文档属性可以通过此方式访问。与上述属性等效的写法可能如下:

frappe.utils.now_datetime() - self.creation

上述提到的 Person.full_name 示例使用 Python 的 f-string 功能以类似方式实现。

注意:对于相对较小的脚本,应优先使用此方法。使用此方法时,请注意不兼容的类型错误。

对内部机制的影响

如果你非常熟悉 Frappe 世界的运作原理,这个功能将会显得相当可预测。

后端 API

DatabaseQuery 方法或 Database API 不会返回虚拟值,因为它们不存在于站点数据库中。

REST API

/api/method/frappe.desk.form.load.getdoc/api/resource API 使用 Document.get_valid_dict,它也会计算虚拟值。这些 API 也用于渲染桌面端表单视图。

数据库

虚拟字段在相应文档类型的表中没有留下任何痕迹。但是,你可能会在存储文档类型元数据的自定义字段表、文档字段表中找到相应记录,以证明它们的存在。

非虚拟文档类型上的虚拟表

注意:此功能仅在夜间版(v16)中可用。此功能被视为实验性功能。

虚拟子表是一种类型为”表”的虚拟字段,在运行时计算。它在许多方面与普通子表行为类似:

  • 虚拟子表出现在父文档下的表单(网格)中。
  • 其行是动态计算的(例如,通过缓存属性或描述符)。
  • 它是只读的(你不能通过常规的 ORM 方法向其中写入数据)。
  • 父文档类型不会持久化这些子行;它们仅存在于内存中。
  • 虚拟表的描述符方法可以返回原始字典或 Document 实例。
  • 从数据库加载时,会触发虚拟子表的描述符来填充子行。

虚拟表对于显示计算/聚合数据或存储在其他位置的关联数据摘要非常有用。

定义虚拟子表

要定义虚拟子表,你需要在父文档类型中添加一个新的字段条目,并将”is virtual”设置为 1。动态获取虚拟表的逻辑必须定义为文档类型控制器上的缓存属性。

注意:对于虚拟表,您不得使用 @property,仅支持 @cached_property 及其他等效的非数据描述符。

class User(…) :
    # This is a cached_property (or a non-data descriptor) returning computed rows
    @cached_property
    def virtual_sessions(self):
        # return a list of dicts or list of Document instances
        sessions = get_session_logs(self.name)
        return sessions

在此示例中,当加载 User 记录时,框架会调用 User.virtual_sessions 来获取子行,然后在表单上下文中初始化一个虚拟子表 virtual_sessions

模块

一个 DocType 总是归属于某个模块,以便于对相关模型进行分组管理。
Frappe 自带了许多内置模块。例如:

  1. Core(核心)- 包含 DocType、DocField、Report、System Settings 等 DocType
  2. Desk(工作台)- 包含 ToDo、Event、Note、Kanban Board 等 DocType
  3. Email(邮件)- 包含 Email Account、Newsletter、Email Group 等 DocType

模块还有助于将代码文件按目录进行分组。由 DocType 生成的控制器文件
会存放在其对应的模块目录中。

frappe
├── commands
├── config
├── core
│   ├── doctype
│   │   ├── doctype
│   │   ├── docfield
│   │   ├── report
│   │   ├── system_settings
│   │   ├── ...
│   │   └── ...
│   ├── page
│   ├── report
│   └── web_form
├── desk
│   ├── doctype
│   │   ├── ...
│   │   ├── event
│   │   ├── kanban_board
│   │   ├── note
│   │   └── todo
│   ├── form
│   ├── page

单据状态

Frappe 使用“文档状态”(Docstatus)的概念来跟踪交易的状态。文档状态始终为以下三个值之一:

  1. 草稿(值:0)
  2. 已提交(值:1)
  3. 已取消(值:2)

不可提交的文档将始终保持在“草稿”状态。可提交的文档可以选择从草稿状态进入“已提交”状态,然后进入“已取消”状态。

处于已提交和已取消状态的文档无法编辑,但有一个例外:对于个别字段,我们可以明确允许编辑,即使文档处于已提交状态。

在后端代码中,我们有一个辅助类 DocStatus,可以按如下方式使用:

import frappe
from frappe.model.docstatus import DocStatus

draft_invoice_names = frappe.get_list(
 "Sales Invoice",
 filters={"docstatus": DocStatus.draft()},
 pluck="name"
)

invoice_doc = frappe.get_doc("Sales Invoice", draft_invoice_names[0])
invoice_doc.docstatus == DocStatus.draft() # -> True
invoice_doc.docstatus.is_draft() # -> True
invoice_doc.docstatus.is_submitted() # -> False
invoice_doc.docstatus.is_cancelled() # -> False

invoice_doc.submit()
invoice_doc.docstatus == DocStatus.submitted() # -> True
invoice_doc.docstatus.is_draft() # -> False
invoice_doc.docstatus.is_submitted() # -> True
invoice_doc.docstatus.is_cancelled() # -> False

invoice_doc.cancel()
invoice_doc.docstatus == DocStatus.cancelled() # -> True
invoice_doc.docstatus.is_draft() # -> False
invoice_doc.docstatus.is_submitted() # -> False
invoice_doc.docstatus.is_cancelled() # -> True

文档状态以整数值的形式存储在数据库的每个 DocType 表中。