doctype-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Frappe 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__.py

DocType 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

文本字段

TypeDescriptionUse Case
Data
Single line text (140 chars)Names, codes, short text
Small Text
Multi-line textShort descriptions
Text
Multi-line text (unlimited)Long descriptions
Text Editor
Rich text with formattingContent, notes
Code
Syntax-highlighted codePython, JS, JSON
HTML Editor
WYSIWYG HTMLEmail templates
Markdown Editor
Markdown inputDocumentation
Password
Masked inputSecrets (stored encrypted)
类型描述适用场景
Data
单行文本(最多140字符)名称、编码、短文本
Small Text
多行短文本简短描述
Text
多行无限制文本长描述内容
Text Editor
带格式的富文本编辑器内容、备注
Code
带语法高亮的代码编辑器Python、JS、JSON代码
HTML Editor
所见即所得HTML编辑器邮件模板
Markdown Editor
Markdown输入框文档编写
Password
掩码输入框敏感信息(加密存储)

Numeric Fields

数值字段

TypeDescriptionUse Case
Int
IntegerCounts, quantities
Float
Decimal numberMeasurements
Currency
Money with precisionPrices, amounts
Percent
0-100 percentageDiscounts, rates
Rating
Star rating (0-1)Reviews, scores
类型描述适用场景
Int
整数计数、数量
Float
小数测量值
Currency
带精度的货币类型价格、金额
Percent
0-100百分比折扣、比率
Rating
星级评分(0-1)评价、得分

Date/Time Fields

日期/时间字段

TypeDescriptionUse Case
Date
Date onlyBirth dates, due dates
Datetime
Date and timeTimestamps
Time
Time onlySchedules
Duration
Time durationTask duration
类型描述适用场景
Date
仅日期出生日期、截止日期
Datetime
日期+时间时间戳
Time
仅时间日程安排
Duration
时间时长任务耗时

Selection Fields

选择类字段

TypeDescriptionUse Case
Select
Dropdown optionsStatus, type
Check
Boolean checkboxFlags, toggles
Autocomplete
Text with suggestionsTags
类型描述适用场景
Select
下拉选项框状态、类型选择
Check
布尔复选框标记、开关
Autocomplete
带建议的文本框标签选择

Link Fields

关联类字段

TypeDescriptionUse Case
Link
Reference to another DocTypeForeign key relationship
Dynamic Link
Reference based on another fieldPolymorphic links
Table
Child table (1-to-many)Line items, details
Table MultiSelect
Many-to-many via linkMultiple selections
类型描述适用场景
Link
关联其他DocType外键关系
Dynamic Link
基于其他字段动态关联多态关联
Table
子表(一对多关系)明细行、详情项
Table MultiSelect
通过关联实现多对多多选关联

Special Fields

特殊字段

TypeDescriptionUse Case
Attach
Single file attachmentDocuments
Attach Image
Image with previewPhotos, logos
Image
Display image from URL fieldGallery
Signature
Signature padApprovals
Geolocation
Map coordinatesLocations
Barcode
Barcode/QR displayInventory
JSON
JSON dataConfiguration
类型描述适用场景
Attach
单个文件附件文档上传
Attach Image
带预览的图片附件照片、Logo
Image
从URL字段显示图片图片展示
Signature
签名输入框审批签名
Geolocation
地图坐标地理位置
Barcode
条形码/二维码显示库存管理
JSON
JSON数据存储配置信息

Layout Fields

布局类字段

TypeDescriptionUse Case
Section Break
Horizontal section dividerForm organization
Column Break
Vertical column dividerMulti-column layout
Tab Break
Tab navigationLarge forms
HTML
Static HTML contentInstructions, headers
Heading
Section headingVisual separation
Button
Clickable buttonActions
类型描述适用场景
Section Break
水平区域分隔符表单内容分组
Column Break
垂直列分隔符多列布局
Tab Break
标签页导航复杂表单分栏
HTML
静态HTML内容说明文字、标题
Heading
区域标题视觉分隔
Button
可点击按钮操作触发

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
undefined
python
undefined

my_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)
undefined
import 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)
undefined

Child 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
    in_list_view
    for key fields
  • Set
    in_standard_filter
    for filterable fields
  • 重要字段放在前面
  • 使用Section Break进行内容分组
  • 复杂表单使用Tab Break分标签页
  • 为关键字段设置
    in_list_view
    属性
  • 为可筛选字段设置
    in_standard_filter
    属性

Performance

性能优化

  • Index frequently queried fields with
    search_index: 1
  • Use
    read_only
    to prevent unnecessary validation
  • Limit child table rows with
    max_attachments
  • 为频繁查询的字段设置
    search_index: 1
    添加索引
  • 使用
    read_only
    避免不必要的验证
  • 通过
    max_attachments
    限制子表行数

Data Integrity

数据完整性

  • Use
    unique: 1
    for unique constraints
  • Set appropriate
    reqd
    (required) flags
  • Use
    depends_on
    for conditional visibility
  • Use
    mandatory_depends_on
    for conditional requirements
  • 使用
    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
}