doctype-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFrappe DocType Patterns
Frappe DocType 模式
Comprehensive guide to creating and configuring DocTypes in Frappe Framework, the core building block for all Frappe applications.
本指南全面介绍Frappe框架中DocType的创建与配置方法,DocType是所有Frappe应用的核心构建块。
When to Use This Skill
何时使用此技能
- Creating new DocTypes
- Adding or modifying fields on DocTypes
- Designing data models and relationships
- Setting up naming patterns and autoname
- Configuring permissions and workflows
- Creating child tables
- Working with Virtual or Single DocTypes
- 创建新的DocType
- 在DocType上添加或修改字段
- 设计数据模型和关系
- 设置命名模式和自动命名规则
- 配置权限与工作流
- 创建子表
- 使用虚拟DocType或单例DocType
DocType Directory Structure
DocType目录结构
When you create a DocType named "My Custom DocType" in module "My Module":
my_app/
└── my_module/
└── doctype/
└── my_custom_doctype/
├── my_custom_doctype.json # DocType definition
├── my_custom_doctype.py # Python controller
├── my_custom_doctype.js # Client script
├── test_my_custom_doctype.py # Test file
└── __init__.py当你在模块"My Module"中创建名为"My Custom DocType"的DocType时,目录结构如下:
my_app/
└── my_module/
└── doctype/
└── my_custom_doctype/
├── my_custom_doctype.json # DocType定义
├── my_custom_doctype.py # Python控制器
├── my_custom_doctype.js # 客户端脚本
├── test_my_custom_doctype.py # 测试文件
└── __init__.pyDocType JSON Structure
DocType JSON结构
json
{
"name": "My Custom DocType",
"module": "My Module",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": ["field1", "field2"],
"fields": [
{
"fieldname": "field1",
"fieldtype": "Data",
"label": "Field 1",
"reqd": 1
}
],
"permissions": [
{
"role": "System Manager",
"read": 1,
"write": 1,
"create": 1,
"delete": 1
}
],
"autoname": "naming_series:",
"naming_rule": "By \"Naming Series\" field",
"is_submittable": 0,
"istable": 0,
"issingle": 0,
"track_changes": 1,
"sort_field": "modified",
"sort_order": "DESC"
}json
{
"name": "My Custom DocType",
"module": "My Module",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": ["field1", "field2"],
"fields": [
{
"fieldname": "field1",
"fieldtype": "Data",
"label": "Field 1",
"reqd": 1
}
],
"permissions": [
{
"role": "System Manager",
"read": 1,
"write": 1,
"create": 1,
"delete": 1
}
],
"autoname": "naming_series:",
"naming_rule": "By \"Naming Series\" field",
"is_submittable": 0,
"istable": 0,
"issingle": 0,
"track_changes": 1,
"sort_field": "modified",
"sort_order": "DESC"
}Field Types Reference
字段类型参考
Text Fields
文本字段
| Type | Description | Use Case |
|---|---|---|
| Single line text (140 chars) | Names, codes, short text |
| Multi-line text | Short descriptions |
| Multi-line text (unlimited) | Long descriptions |
| Rich text with formatting | Content, notes |
| Syntax-highlighted code | Python, JS, JSON |
| WYSIWYG HTML | Email templates |
| Markdown input | Documentation |
| Masked input | Secrets (stored encrypted) |
| 类型 | 描述 | 适用场景 |
|---|---|---|
| 单行文本(最多140字符) | 名称、编码、短文本 |
| 多行短文本 | 简短描述 |
| 多行无限制文本 | 长描述内容 |
| 带格式的富文本编辑器 | 内容、备注 |
| 带语法高亮的代码编辑器 | Python、JS、JSON代码 |
| 所见即所得HTML编辑器 | 邮件模板 |
| Markdown输入框 | 文档编写 |
| 掩码输入框 | 敏感信息(加密存储) |
Numeric Fields
数值字段
| Type | Description | Use Case |
|---|---|---|
| Integer | Counts, quantities |
| Decimal number | Measurements |
| Money with precision | Prices, amounts |
| 0-100 percentage | Discounts, rates |
| Star rating (0-1) | Reviews, scores |
| 类型 | 描述 | 适用场景 |
|---|---|---|
| 整数 | 计数、数量 |
| 小数 | 测量值 |
| 带精度的货币类型 | 价格、金额 |
| 0-100百分比 | 折扣、比率 |
| 星级评分(0-1) | 评价、得分 |
Date/Time Fields
日期/时间字段
| Type | Description | Use Case |
|---|---|---|
| Date only | Birth dates, due dates |
| Date and time | Timestamps |
| Time only | Schedules |
| Time duration | Task duration |
| 类型 | 描述 | 适用场景 |
|---|---|---|
| 仅日期 | 出生日期、截止日期 |
| 日期+时间 | 时间戳 |
| 仅时间 | 日程安排 |
| 时间时长 | 任务耗时 |
Selection Fields
选择类字段
| Type | Description | Use Case |
|---|---|---|
| Dropdown options | Status, type |
| Boolean checkbox | Flags, toggles |
| Text with suggestions | Tags |
| 类型 | 描述 | 适用场景 |
|---|---|---|
| 下拉选项框 | 状态、类型选择 |
| 布尔复选框 | 标记、开关 |
| 带建议的文本框 | 标签选择 |
Link Fields
关联类字段
| Type | Description | Use Case |
|---|---|---|
| Reference to another DocType | Foreign key relationship |
| Reference based on another field | Polymorphic links |
| Child table (1-to-many) | Line items, details |
| Many-to-many via link | Multiple selections |
| 类型 | 描述 | 适用场景 |
|---|---|---|
| 关联其他DocType | 外键关系 |
| 基于其他字段动态关联 | 多态关联 |
| 子表(一对多关系) | 明细行、详情项 |
| 通过关联实现多对多 | 多选关联 |
Special Fields
特殊字段
| Type | Description | Use Case |
|---|---|---|
| Single file attachment | Documents |
| Image with preview | Photos, logos |
| Display image from URL field | Gallery |
| Signature pad | Approvals |
| Map coordinates | Locations |
| Barcode/QR display | Inventory |
| JSON data | Configuration |
| 类型 | 描述 | 适用场景 |
|---|---|---|
| 单个文件附件 | 文档上传 |
| 带预览的图片附件 | 照片、Logo |
| 从URL字段显示图片 | 图片展示 |
| 签名输入框 | 审批签名 |
| 地图坐标 | 地理位置 |
| 条形码/二维码显示 | 库存管理 |
| JSON数据存储 | 配置信息 |
Layout Fields
布局类字段
| Type | Description | Use Case |
|---|---|---|
| Horizontal section divider | Form organization |
| Vertical column divider | Multi-column layout |
| Tab navigation | Large forms |
| Static HTML content | Instructions, headers |
| Section heading | Visual separation |
| Clickable button | Actions |
| 类型 | 描述 | 适用场景 |
|---|---|---|
| 水平区域分隔符 | 表单内容分组 |
| 垂直列分隔符 | 多列布局 |
| 标签页导航 | 复杂表单分栏 |
| 静态HTML内容 | 说明文字、标题 |
| 区域标题 | 视觉分隔 |
| 可点击按钮 | 操作触发 |
Field Options
字段配置选项
Common Field Properties
通用字段属性
json
{
"fieldname": "customer",
"fieldtype": "Link",
"label": "Customer",
"options": "Customer",
"reqd": 1,
"unique": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"in_global_search": 1,
"bold": 1,
"read_only": 0,
"hidden": 0,
"print_hide": 0,
"no_copy": 0,
"allow_in_quick_entry": 1,
"translatable": 0,
"default": "",
"description": "Select the customer",
"depends_on": "eval:doc.is_customer",
"mandatory_depends_on": "eval:doc.status=='Active'",
"read_only_depends_on": "eval:doc.docstatus==1"
}json
{
"fieldname": "customer",
"fieldtype": "Link",
"label": "Customer",
"options": "Customer",
"reqd": 1,
"unique": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"in_global_search": 1,
"bold": 1,
"read_only": 0,
"hidden": 0,
"print_hide": 0,
"no_copy": 0,
"allow_in_quick_entry": 1,
"translatable": 0,
"default": "",
"description": "Select the customer",
"depends_on": "eval:doc.is_customer",
"mandatory_depends_on": "eval:doc.status=='Active'",
"read_only_depends_on": "eval:doc.docstatus==1"
}Link Field Options
关联字段配置
json
{
"fieldname": "customer",
"fieldtype": "Link",
"options": "Customer",
"filters": {
"disabled": 0,
"customer_type": "Company"
},
"ignore_user_permissions": 0
}json
{
"fieldname": "customer",
"fieldtype": "Link",
"options": "Customer",
"filters": {
"disabled": 0,
"customer_type": "Company"
},
"ignore_user_permissions": 0
}Select Field Options
下拉字段配置
json
{
"fieldname": "status",
"fieldtype": "Select",
"options": "\nDraft\nPending\nApproved\nRejected",
"default": "Draft"
}json
{
"fieldname": "status",
"fieldtype": "Select",
"options": "\nDraft\nPending\nApproved\nRejected",
"default": "Draft"
}Dynamic Link
动态关联字段
json
{
"fieldname": "party_type",
"fieldtype": "Link",
"options": "DocType"
},
{
"fieldname": "party",
"fieldtype": "Dynamic Link",
"options": "party_type"
}json
{
"fieldname": "party_type",
"fieldtype": "Link",
"options": "DocType"
},
{
"fieldname": "party",
"fieldtype": "Dynamic Link",
"options": "party_type"
}Naming Patterns (autoname)
命名模式(autoname)
Naming Series
命名序列
json
{
"autoname": "naming_series:",
"naming_rule": "By \"Naming Series\" field"
}Add a naming_series field:
json
{
"fieldname": "naming_series",
"fieldtype": "Select",
"options": "INV-.YYYY.-\nINV-.MM.-.YYYY.-",
"default": "INV-.YYYY.-"
}json
{
"autoname": "naming_series:",
"naming_rule": "By \"Naming Series\" field"
}添加命名序列字段:
json
{
"fieldname": "naming_series",
"fieldtype": "Select",
"options": "INV-.YYYY.-\nINV-.MM.-.YYYY.-",
"default": "INV-.YYYY.-"
}Field-Based Naming
基于字段命名
json
{
"autoname": "field:customer_code",
"naming_rule": "By fieldname"
}json
{
"autoname": "field:customer_code",
"naming_rule": "By fieldname"
}Expression-Based
表达式命名
json
{
"autoname": "format:{customer_type}-{###}",
"naming_rule": "Expression"
}json
{
"autoname": "format:{customer_type}-{###}",
"naming_rule": "Expression"
}Hash/Random
哈希/随机命名
json
{
"autoname": "hash",
"naming_rule": "Random"
}json
{
"autoname": "hash",
"naming_rule": "Random"
}Prompt (Manual)
手动输入命名
json
{
"autoname": "Prompt",
"naming_rule": "Set by user"
}json
{
"autoname": "Prompt",
"naming_rule": "Set by user"
}Controller Lifecycle Hooks
控制器生命周期钩子
python
undefinedpython
undefinedmy_doctype.py
my_doctype.py
import frappe
from frappe.model.document import Document
class MyDocType(Document):
# ===== BEFORE DATABASE OPERATIONS =====
def autoname(self):
"""Set the document name before saving"""
self.name = f"{self.prefix}-{frappe.generate_hash()[:8]}"
def before_naming(self):
"""Called before autoname, can modify naming logic"""
pass
def validate(self):
"""Validate data before save (called on insert and update)"""
self.validate_dates()
self.calculate_totals()
def before_validate(self):
"""Called before validate"""
pass
def before_save(self):
"""Called before document is saved to database"""
self.modified_by_script = True
def before_insert(self):
"""Called before new document is inserted"""
self.set_defaults()
# ===== AFTER DATABASE OPERATIONS =====
def after_insert(self):
"""Called after new document is inserted"""
self.notify_users()
def on_update(self):
"""Called after document is saved (insert or update)"""
self.update_related_docs()
def after_save(self):
"""Called after on_update, always runs"""
pass
def on_change(self):
"""Called when document changes in database"""
pass
# ===== SUBMISSION WORKFLOW =====
def before_submit(self):
"""Called before document is submitted"""
self.validate_for_submit()
def on_submit(self):
"""Called after document is submitted"""
self.create_gl_entries()
def before_cancel(self):
"""Called before document is cancelled"""
self.validate_cancellation()
def on_cancel(self):
"""Called after document is cancelled"""
self.reverse_gl_entries()
def on_update_after_submit(self):
"""Called when submitted doc is updated (limited fields)"""
pass
# ===== DELETION =====
def before_delete(self):
"""Called before document is deleted"""
self.check_dependencies()
def after_delete(self):
"""Called after document is deleted"""
self.cleanup_attachments()
def on_trash(self):
"""Called when document is trashed"""
pass
def after_restore(self):
"""Called after document is restored from trash"""
pass
# ===== CUSTOM METHODS =====
def validate_dates(self):
if self.end_date and self.start_date > self.end_date:
frappe.throw("End date cannot be before start date")
def calculate_totals(self):
self.total = sum(d.amount for d in self.items)undefinedimport frappe
from frappe.model.document import Document
class MyDocType(Document):
# ===== 数据库操作前 =====
def autoname(self):
"""Set the document name before saving"""
self.name = f"{self.prefix}-{frappe.generate_hash()[:8]}"
def before_naming(self):
"""Called before autoname, can modify naming logic"""
pass
def validate(self):
"""Validate data before save (called on insert and update)"""
self.validate_dates()
self.calculate_totals()
def before_validate(self):
"""Called before validate"""
pass
def before_save(self):
"""Called before document is saved to database"""
self.modified_by_script = True
def before_insert(self):
"""Called before new document is inserted"""
self.set_defaults()
# ===== 数据库操作后 =====
def after_insert(self):
"""Called after new document is inserted"""
self.notify_users()
def on_update(self):
"""Called after document is saved (insert or update)"""
self.update_related_docs()
def after_save(self):
"""Called after on_update, always runs"""
pass
def on_change(self):
"""Called when document changes in database"""
pass
# ===== 提交工作流 =====
def before_submit(self):
"""Called before document is submitted"""
self.validate_for_submit()
def on_submit(self):
"""Called after document is submitted"""
self.create_gl_entries()
def before_cancel(self):
"""Called before document is cancelled"""
self.validate_cancellation()
def on_cancel(self):
"""Called after document is cancelled"""
self.reverse_gl_entries()
def on_update_after_submit(self):
"""Called when submitted doc is updated (limited fields)"""
pass
# ===== 删除操作 =====
def before_delete(self):
"""Called before document is deleted"""
self.check_dependencies()
def after_delete(self):
"""Called after document is deleted"""
self.cleanup_attachments()
def on_trash(self):
"""Called when document is trashed"""
pass
def after_restore(self):
"""Called after document is restored from trash"""
pass
# ===== 自定义方法 =====
def validate_dates(self):
if self.end_date and self.start_date > self.end_date:
frappe.throw("End date cannot be before start date")
def calculate_totals(self):
self.total = sum(d.amount for d in self.items)undefinedChild Table (Table Field)
子表(Table字段)
Parent DocType
父DocType配置
json
{
"fieldname": "items",
"fieldtype": "Table",
"label": "Items",
"options": "My DocType Item",
"reqd": 1
}json
{
"fieldname": "items",
"fieldtype": "Table",
"label": "Items",
"options": "My DocType Item",
"reqd": 1
}Child DocType JSON
子DocType JSON配置
json
{
"name": "My DocType Item",
"module": "My Module",
"doctype": "DocType",
"istable": 1,
"editable_grid": 1,
"fields": [
{
"fieldname": "item",
"fieldtype": "Link",
"options": "Item",
"in_list_view": 1,
"reqd": 1
},
{
"fieldname": "qty",
"fieldtype": "Float",
"in_list_view": 1
},
{
"fieldname": "rate",
"fieldtype": "Currency",
"in_list_view": 1
},
{
"fieldname": "amount",
"fieldtype": "Currency",
"in_list_view": 1,
"read_only": 1
}
]
}json
{
"name": "My DocType Item",
"module": "My Module",
"doctype": "DocType",
"istable": 1,
"editable_grid": 1,
"fields": [
{
"fieldname": "item",
"fieldtype": "Link",
"options": "Item",
"in_list_view": 1,
"reqd": 1
},
{
"fieldname": "qty",
"fieldtype": "Float",
"in_list_view": 1
},
{
"fieldname": "rate",
"fieldtype": "Currency",
"in_list_view": 1
},
{
"fieldname": "amount",
"fieldtype": "Currency",
"in_list_view": 1,
"read_only": 1
}
]
}Single DocType (Settings)
单例DocType(设置类)
For application settings that have only one record:
json
{
"name": "My App Settings",
"module": "My Module",
"doctype": "DocType",
"issingle": 1,
"fields": [
{
"fieldname": "enable_feature",
"fieldtype": "Check",
"label": "Enable Feature"
},
{
"fieldname": "api_key",
"fieldtype": "Password",
"label": "API Key"
}
]
}Access in code:
python
settings = frappe.get_single("My App Settings")
if settings.enable_feature:
do_something()用于仅需一条记录的应用设置:
json
{
"name": "My App Settings",
"module": "My Module",
"doctype": "DocType",
"issingle": 1,
"fields": [
{
"fieldname": "enable_feature",
"fieldtype": "Check",
"label": "Enable Feature"
},
{
"fieldname": "api_key",
"fieldtype": "Password",
"label": "API Key"
}
]
}代码中访问:
python
settings = frappe.get_single("My App Settings")
if settings.enable_feature:
do_something()Virtual DocType
虚拟DocType
DocType without database table, computed on-the-fly:
json
{
"name": "My Virtual DocType",
"module": "My Module",
"doctype": "DocType",
"is_virtual": 1
}Controller:
python
class MyVirtualDocType(Document):
@staticmethod
def get_list(args):
# Return list of virtual documents
return [{"name": "doc1", "value": 100}]
@staticmethod
def get_count(args):
return len(MyVirtualDocType.get_list(args))
@staticmethod
def get_stats(args):
return {}无数据库表,动态计算生成的DocType:
json
{
"name": "My Virtual DocType",
"module": "My Module",
"doctype": "DocType",
"is_virtual": 1
}控制器实现:
python
class MyVirtualDocType(Document):
@staticmethod
def get_list(args):
# Return list of virtual documents
return [{"name": "doc1", "value": 100}]
@staticmethod
def get_count(args):
return len(MyVirtualDocType.get_list(args))
@staticmethod
def get_stats(args):
return {}Permissions
权限配置
json
{
"permissions": [
{
"role": "System Manager",
"read": 1,
"write": 1,
"create": 1,
"delete": 1,
"submit": 1,
"cancel": 1,
"amend": 1,
"report": 1,
"export": 1,
"import": 1,
"share": 1,
"print": 1,
"email": 1
},
{
"role": "Sales User",
"read": 1,
"write": 1,
"create": 1,
"if_owner": 1
}
]
}json
{
"permissions": [
{
"role": "System Manager",
"read": 1,
"write": 1,
"create": 1,
"delete": 1,
"submit": 1,
"cancel": 1,
"amend": 1,
"report": 1,
"export": 1,
"import": 1,
"share": 1,
"print": 1,
"email": 1
},
{
"role": "Sales User",
"read": 1,
"write": 1,
"create": 1,
"if_owner": 1
}
]
}Best Practices
最佳实践
Naming Conventions
命名规范
- Use singular names: "Customer" not "Customers"
- Use Title Case with spaces: "Sales Invoice"
- Fieldnames use snake_case:
customer_name
- 使用单数名称:例如用"Customer"而非"Customers"
- 使用标题大小写加空格:例如"Sales Invoice"
- 字段名使用蛇形命名法:
customer_name
Field Design
字段设计
- Put most important fields first
- Use Section Breaks to organize
- Use Tab Breaks for complex forms
- Set for key fields
in_list_view - Set for filterable fields
in_standard_filter
- 重要字段放在前面
- 使用Section Break进行内容分组
- 复杂表单使用Tab Break分标签页
- 为关键字段设置属性
in_list_view - 为可筛选字段设置属性
in_standard_filter
Performance
性能优化
- Index frequently queried fields with
search_index: 1 - Use to prevent unnecessary validation
read_only - Limit child table rows with
max_attachments
- 为频繁查询的字段设置添加索引
search_index: 1 - 使用避免不必要的验证
read_only - 通过限制子表行数
max_attachments
Data Integrity
数据完整性
- Use for unique constraints
unique: 1 - Set appropriate (required) flags
reqd - Use for conditional visibility
depends_on - Use for conditional requirements
mandatory_depends_on
- 使用设置唯一约束
unique: 1 - 合理设置(必填)标记
reqd - 使用实现条件可见性
depends_on - 使用实现条件必填
mandatory_depends_on
Common Patterns
常见模式
Status Field Pattern
状态字段模式
json
{
"fieldname": "status",
"fieldtype": "Select",
"options": "\nDraft\nPending Approval\nApproved\nRejected",
"default": "Draft",
"in_list_view": 1,
"in_standard_filter": 1,
"read_only": 1,
"allow_on_submit": 1
}json
{
"fieldname": "status",
"fieldtype": "Select",
"options": "\nDraft\nPending Approval\nApproved\nRejected",
"default": "Draft",
"in_list_view": 1,
"in_standard_filter": 1,
"read_only": 1,
"allow_on_submit": 1
}Amount Calculation Pattern
金额计算模式
json
[
{"fieldname": "qty", "fieldtype": "Float"},
{"fieldname": "rate", "fieldtype": "Currency"},
{"fieldname": "amount", "fieldtype": "Currency", "read_only": 1}
]With controller:
python
def validate(self):
for item in self.items:
item.amount = flt(item.qty) * flt(item.rate)
self.total = sum(item.amount for item in self.items)json
[
{"fieldname": "qty", "fieldtype": "Float"},
{"fieldname": "rate", "fieldtype": "Currency"},
{"fieldname": "amount", "fieldtype": "Currency", "read_only": 1}
]配合控制器代码:
python
def validate(self):
for item in self.items:
item.amount = flt(item.qty) * flt(item.rate)
self.total = sum(item.amount for item in self.items)Linked Document Pattern
关联文档模式
json
{
"fieldname": "customer",
"fieldtype": "Link",
"options": "Customer",
"reqd": 1
},
{
"fieldname": "customer_name",
"fieldtype": "Data",
"fetch_from": "customer.customer_name",
"read_only": 1
}json
{
"fieldname": "customer",
"fieldtype": "Link",
"options": "Customer",
"reqd": 1
},
{
"fieldname": "customer_name",
"fieldtype": "Data",
"fetch_from": "customer.customer_name",
"read_only": 1
}