ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Frappe 自定义字段(Custom Field)完整指南:运行时扩展 DocType 元数据的核心机制

2026/9/15 22:32:31 拓冰建站 浏览量
Frappe 自定义字段(Custom Field)完整指南:运行时扩展 DocType 元数据的核心机制 Frappe 自定义字段Custom Field完整指南运行时扩展 DocType 元数据的核心机制【免费下载链接】frappeLow code web framework for real world applications, in Python and Javascript项目地址: https://gitcode.com/GitHub_Trending/fr/frappe自定义字段Custom Field是 Frappe 低代码框架中最常用的元数据扩展手段它由用户在运行时创建不属于 DocType 的标准字段定义但在加载 DocType 元数据时会作为字段自动合并进表结构。本文基于 Custom Field 官方说明 与 custom_field.py 源码完整讲解自定义字段的创建入口、命名与排序规则、全部配置参数、底层元数据加载链路以及通过 Python API 和 Patch 批量维护自定义字段的工程实践帮助你真正掌握不改 DocType 定义也能加字段的核心能力。一、什么是自定义字段从官方说明说起官方 README 给出了最精炼的定义Custom Field added by the user (not part of DocType but part of table). Custom fields are automatically added to the DocType when they are loaded as metadata.翻译过来即两个关键事实它是用户或 App添加的字段不属于 DocType 源码中的字段定义而是独立的Custom Field文档Doctype 定义每条记录代表一个追加到目标 DocType 的字段当 DocType 的元数据被加载时自定义字段会自动合并进该 DocType 的字段列表无需手工改表。理解这两点就能把握自定义字段在设计上的本质DocType 元数据 标准字段源码定义 自定义字段数据库记录 Property Setter属性覆盖。自定义字段让字段扩展从改代码、发版本变成了加记录、刷新即生效这正是 Frappe 低代码哲学的体现。从源码结构看自定义字段与 Customize Form、Property Setter 共同构成了 frappe/custom 模块的定制化三件套本篇文章聚焦其中的 Custom Field。二、创建自定义字段桌面端操作路径在 Desk 界面进入Setup → Customize Form → Custom Field或直接在 Awesome Bar 搜索 Custom Field List点击新建即可。其表单字段布局在 custom_field.json 中定义主要分区如下。2.1 DocType 选择器的内置过滤创建时最关键的一步是选择目标 DocType。表单 JScustom_field.js通过set_query(dt, ...)对可选项施加了严格过滤仅允许非 Single、非 Custom 的标准 DocTypeissingle 0且custom 0排除核心 DocTypenot in frappe.model.core_doctypes_list限制在当前激活的 Domain 内restrict_to_domain in frappe.boot.active_domains非 Administrator 用户还不能选择 Core、Custom 模块下的 DocType。这解释了官方说明中part of DocType but part of table的深层含义自定义字段只能追加到标准 DocType由源码定义上而纯自定义 DocTypecustom 1直接编辑字段即可不需要也无法添加 Custom Field。后端同样做了校验——白名单函数get_fields_label在 custom_field.py 中对核心 DocType 直接返回 Custom Fields cannot be added to core DocTypes。2.2 表单交互细节Label必填且不能包含!#$%^*()-[]\;,./{}|:?等特殊字符输入非法字符时表单会直接清空并给出红色提示Fieldname由 Label 自动生成界面只读支持重命名按钮见下文Insert After选择新字段插入到哪个既有字段之后选项由get_fields_label白名单动态返回若选择append则追加到末尾系统生成字段警示当is_system_generated为真时表单顶部会显示黄色警告提示该字段可能被未来更新覆盖建议改用 Customize Form 修改。三、字段命名与排序autoname 与 validate 的核心逻辑3.1 自动命名规则Custom Field文档的命名由autoname方法决定custom_field.pydef autoname(self): self.set_fieldname() self.name self.dt - self.fieldname即主键格式为DocType-fieldname例如Address-_test_custom_field_1测试用例 test_custom_field.py 直接断言了这一点。set_fieldname同文件 L138-L169的生成规则值得注意若未指定fieldname则由 Label 派生去掉特殊字符、空格替换为下划线只保留字母、数字和下划线自动添加custom_前缀因此桌面端创建的字段名形如custom_xxx统一转为小写若与受限字段名name、parent、creation、modified、parentfield、parenttype、file_list、flags、docstatus等冲突则在末尾追加1。3.2 Insert After 与排序在validate中L174-L196新建或insert_after append时会重新读取目标 DocType 的完整元数据若fieldname已存在于目标 DocType直接抛错A field with the name ... already existsappend会被改写为最后一个字段名计算并写入idx 字段列表中 insert_after 的位置 1从而保证字段按指定位置插入。关于排序的健壮性测试 test_custom_field.py 覆盖了后插入的字段位于先插入字段之后、未知 insert_after 的字段排到最后等边界情况Section/Column/Tab Break 的插入顺序也有专门测试L89-L201保证自定义分区不会打乱标准分区结构。3.3 字段类型变更保护validate中还有一个易被忽略的约束L198-L208非虚拟字段不允许随意变更fieldtype变更会经CustomizeForm.allow_fieldtype_change(old, new)校验非法变更直接抛错防止破坏已存在的数据库列类型。四、自定义字段的完整配置参数官方 README 虽短但 custom_field.json 中定义的全部字段构成了这份文档的实质内容。以下按功能分组整理参数名与取值均以当前仓库为准。4.1 基础定义必填项参数类型说明dtLink (DocType)目标 DocType必填带搜索索引labelData显示标签必填无 fieldname 时长度上限 255fieldnameData内部字段名通常由 Label 自动生成只读fieldtypeSelect字段类型必填默认Data可选值见下insert_afterSelect插入到哪个字段之后选项由目标 DocType 动态提供moduleLink (Module Def)所属模块用于导出场景placeholderData输入框占位提示descriptionText字段描述显示在表单字段下方4.2 字段类型fieldtype 全部可选值定义于 custom_field.json 的options属性Autocomplete, Attach, Attach Image, Attachment Gallery, Barcode, Button, Check, Code, Color, Column Break, Currency, Data, Date, Datetime, Duration, Dynamic Link, Float, Fold, Geolocation, Heading, HTML, HTML Editor, Icon, Image, Int, JSON, Link, Long Text, Markdown Editor, Password, Percent, Phone, Read Only, Rating, Section Break, Select, Signature, Small Text, Tab Break, Table, Table MultiSelect, Text, Text Editor, Time共 44 种类型与 DF.py 类型定义 一一对应。其中Section Break、Column Break、Tab Break、Fold、Heading属于布局型字段不存储数据Link、Dynamic Link、Table等属于关联型字段。4.3 数值与精度参数适用类型说明precisionFloat、Currency、Percent非标准小数位可选 09留空则用系统默认min_value/max_valueInt、Float、Currency、Percent数值上下限校验non_negativeInt、Float、Currency禁止负数lengthData、Link、Dynamic Link、Password、Select、Read Only、Attach、Attach Image、Int字段长度数据库列宽4.4 Options 与取值来源optionsSmall Text是 Small Text 类型取值随fieldtype而变化Link / Dynamic Linkoptions填目标 DocType 名称如CustomerDynamic Link 则填保存 DocType 名称的字段名表单 JS 中的帮助文案对此有明确说明Select每行一个选项如Option 1\nOption 2\nOption 3Currency可填货币单位如INR、USDAttach Image / Attach可填文件目录等其他类型一般留空。配套的sort_options仅 Select 类型可对选项自动排序link_filtersJSON仅 Attachment Gallery 与 Link 类型可为 Link 查询与附件过滤追加条件。4.5 行为与校验参数说明reqd必填Mandatoryunique唯一约束read_only只读depends_on/mandatory_depends_on/read_only_depends_on表达式条件支持eval:开头如eval:doc.docstatus1default默认值set_only_once仅允许在新建时赋值一次allow_on_submit允许提交Submit后编辑no_copy复制文档时不复制该字段ignore_user_permissions仅 Link 类型忽略用户权限过滤ignore_versioning非虚拟字段可用不记录版本变更is_virtual虚拟字段不落库非 Link 类型可用仅存于内存中计算permlevel权限级别配合权限规则做字段级权限控制fetch_from/fetch_if_empty从关联文档取值自动填充fetch_if_empty勾选后每次保存都会重新拉取4.6 显示与可见性参数说明hidden表单中隐藏print_hide/print_hide_if_no_value打印/PDF 中隐藏后者仅 Int、Float、Currency、Percent 可用print_width、width打印宽度 / 显示宽度columns列表或 Grid 中的列数总和需小于 11alignment文本对齐可选 Left / Center / RightData、Int、Float、Currency、Percentbold粗体显示in_list_view显示在列表视图中in_standard_filter作为标准过滤器in_preview显示在列表预览中Table 类除外in_global_search参与全局搜索Data、Select、Table、Text、Text Editor、Link、Small Text、Long Text、Read Only、Heading、Dynamic Linkreport_hide报表中隐藏show_dashboard显示在 Dashboard 区域hide_borderSection Break隐藏分区边框hide_days/hide_secondsDuration时长字段隐藏天数 / 秒数4.7 高级与系统属性参数说明translatable可翻译仅 Data、Select、Text、Small Text、Text Editor后端validate会调用supports_translation校验不支持的自动置 0search_index建数据库索引表单中隐藏mask掩码输入Data、Date、Link、Currency 等约 16 种类型可用ignore_xss_filter跳过 XSS 转义options、default 字段本身默认ignore_xss_filter: 1collapsible/collapsible_depends_onSection Break 可折叠及折叠条件button_colorButton 类型按钮颜色Default / Primary / Info / Success / Warning / Dangeris_system_generated系统生成标记只读见下is_app_disabled属主 App 被禁用时自动置位只读五、系统生成字段与可见性控制is_system_generated标记了字段来源由 App 通过代码批量创建的自定义字段默认is_system_generatedTrue这类字段可以被 App 更新覆盖表单顶部的黄色警告即为此设计。若想在 App 更新时保留自己的修改应使用 Customize Form 而非直接编辑。当属主 App 被禁用时字段会被标记is_app_disabled并借助is_field_hidden_by_app在元数据加载阶段被过滤掉——这一逻辑体现在 meta.py 的 add_custom_fields 中查询Custom Field后仅保留未被 App 禁用过滤的字段。相关行为由 test_customization_visibility.py 等测试覆盖。六、底层原理自定义字段如何自动加入 DocType官方说明中automatically added to the DocType when loaded as metadata的实现位于 meta.py 的add_custom_fieldsL408-L426def add_custom_fields(self): if not frappe.db.table_exists(Custom Field): return custom_fields frappe.db.get_values( Custom Field, filters{dt: self.name}, fieldname*, as_dictTrue, order_byidx, update{is_custom_field: 1}, ) if not custom_fields: return custom_fields [field for field in custom_fields if not is_field_hidden_by_app(field)] self.extend(fields, custom_fields)关键点有三按idx排序查询保证自定义字段在合并后保持表单中的插入顺序统一打上is_custom_field: 1标记使其在元数据层面与标准字段可区分跳过被 App 禁用隐藏的字段。保存时的元数据同步保存on_updatecustom_field.py时调用validate_fields_for_doctype(self.dt)校验字段定义调用frappe.clear_cache(doctypeself.dt)清理目标 DocType 缓存调用frappe.db.updatedb(self.dt)触发 DDL为数据库表实际新增列。因此自定义字段保存后立刻生效表结构被更新元数据缓存被刷新表单、报表、导入导出全部可用——但这一切在frappe.flags.in_create_custom_fields开启时会被跳过见下文批量场景。七、批量维护hooks 与 Python API除桌面端外Frappe 提供了一套完整的代码级 APIcustom_field.py是开发 App 与编写 Patch 的首选方式。7.1 创建单个字段from frappe.custom.doctype.custom_field.custom_field import create_custom_field create_custom_field( Sales Invoice, { label: Customer Reference, fieldtype: Data, insert_after: customer, reqd: 1, }, )函数签名create_custom_field(doctype, df, ignore_validateFalse, is_system_generatedTrue)。注意默认is_system_generatedTrue且内部会先检查同名字段是否已存在避免重复创建。7.2 批量创建 / 更新推荐create_custom_fields(custom_fields, ignore_validateFalse, updateTrue)支持一次处理多个 DocType 与多个字段create_custom_fields( { Sales Invoice: [ {fieldname: custom_po_no, label: PO No, fieldtype: Data, insert_after: po_no}, {fieldname: custom_priority, label: Priority, fieldtype: Select, options: Low\nMedium\nHigh, insert_after: custom_po_no}, ], (Address, Contact): [ # 元组可同时作用于多个 DocType {fieldname: custom_region, label: Region, fieldtype: Data}, ], }, ignore_validateTrue, # 安装向导等场景强制跳过校验 )其实现要点L330-L396全程设置frappe.flags.in_create_custom_fields True抑制单条on_update中的逐次 DDL 与缓存刷新避免大量重复操作先一次性查询所有已存在的自定义字段get_existing_custom_fields做差量判断不存在的创建已存在的按updateTrue做增量更新批量操作结束后统一对涉及 DocType 执行clear_cache与updatedbDuplicateEntryError会被静默吞掉保证幂等frappe.flags.in_setup_wizard时强制ignore_validateTrue。7.3 删除from frappe.custom.doctype.custom_field.custom_field import delete_custom_fields # 常规删除走 on_trash 钩子会清理 Property Setter 与 DocType Layout 引用 delete_custom_fields({Address: [custom_a, custom_b]}) # 快速删除跳过钩子仅删记录并清缓存 delete_custom_fields({ToDo: [{fieldname: cf_1}]}, bypass_hooksTrue)on_trashL230-L253会做三件额外清理校验字段删除权限Administrator 创建的字段只能由 Administrator 删除、级联删除关联的 Property Setter、从所有引用该字段的 DocType Layout 中移除。测试 test_custom_field.py 的test_delete_custom_fields验证了这两条路径及 Property Setter 的联动清理。7.4 在 hooks.py 中声明推荐做法为便于管理和追踪Frappe 支持在 App 的hooks.py中集中声明# your_app/hooks.py custom_fields { Sales Invoice: [ {fieldname: custom_vat_no, label: VAT Number, fieldtype: Data, insert_after: taxes}, ], }bench migrate时会自动应用这些字段。使用 hooks 声明意味着字段由 App 管理、随版本迭代且自动带上is_system_generated标记——这与官方由用户添加、作为元数据自动合并的定位完全一致也是社区 App 的标准做法。八、重命名与删除的边界行为8.1 重命名字段名表单中的 Rename Fieldname 按钮调用白名单方法rename_fieldnamecustom_field.py仅限 System Manager 使用且系统生成字段不可重命名。重命名会校验新字段名不与目标表已有列冲突调用frappe.db.rename_column实际改列更新__Auth表中 Password 类字段的引用_update_fieldname_referencesL444-L458同步修正其他自定义字段的insert_after引用并清缓存。对应测试test_custom_field_renamingtest_custom_field.py 的 L203-L221验证了重命名后新旧字段值迁移的正确性。8.2 不可为的场景核心 DocType白名单方法直接拒绝提示Custom Fields cannot be added to core DocTypes自定义 DocTypemeta.custom True提示只能给标准 DocType 添加Label 为空且不是布局字段set_fieldname抛出 Label is mandatory。九、实战案例完整走一遍自定义字段生命周期以给 Address DocType 增加一个备注字段为例串联上文所有知识点# 1. 创建自动命名 Address-custom_remarks插入到 phone 之后 from frappe.custom.doctype.custom_field.custom_field import create_custom_field cf create_custom_field( Address, { label: Remarks, fieldtype: Small Text, insert_after: phone, in_list_view: 1, }, ) assert cf.name Address-custom_remarks # autoname 规则 # 2. 加载元数据时自动合并无需改任何源码 meta frappe.get_meta(Address) assert meta.get_field(custom_remarks).is_custom_field 1 # 3. 更新属性如改为必填 cf.reqd 1 cf.save() # 触发 clear_cache updatedb # 4. 清理连同 Property Setter 一并删除 from frappe.custom.doctype.custom_field.custom_field import delete_custom_fields delete_custom_fields({Address: [custom_remarks]})整个流程中唯一不可跳过的是 DDL 同步字段一旦创建目标表的列结构即被frappe.db.updatedb更新这也是Custom fields are automatically added to the DocType在数据库层面的落点。十、总结与延伸阅读自定义字段把给 DocType 加字段从一次代码发布简化为一次元数据记录操作其价值在于业务建模能力对非开发者开放同时通过is_system_generated、hooks 声明与批量 API 为开发者保留了完全可编程、可版本化的高级路径。核心文件速览官方 README本文主体自定义字段的权威定义custom_field.py命名、校验、同步、批量 API 全实现custom_field.jsonCustom Field 自身的 DocType 定义与全部参数custom_field.js桌面端表单交互与 DocType 过滤meta.py add_custom_fields元数据加载期合并自定义字段的底层机制test_custom_field.py创建、排序、重命名、删除的完整测试若需要修改既有字段的属性而非新增字段可以进一步研究 Customize Form 与 Property Setter 的实现它们与 Custom Field 共同构成 Frappe 运行时定制化的完整闭环。【免费下载链接】frappeLow code web framework for real world applications, in Python and Javascript项目地址: https://gitcode.com/GitHub_Trending/fr/frappe创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考