虚拟单据字段

虚拟文档字段(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