# DocType的类型
让我们通过创建更多的DocTypes来了解框架中不同类型的DocType。
# 图书馆会员资格
让我们创建另一个DocType:图书馆会员资格。它将包含以下字段:
- 图书馆成员(链接,必填)
- 全名(数据,只读)
- 起始日期(日期)
- 结束日期(日期)
- 已支付(勾选)
它将启用可提交。它的命名将设置为LMS.#####,并且限制为图书馆管理员角色。同时,在视图设置部分将标题字段设置为full_name。

链接字段图书馆成员类似于其他框架中的外键列。它将允许您将值链接到另一个DocType中的记录。在这种情况下,它链接到图书馆成员DocType的记录。
全名字段是一个只读字段,它将自动从链接记录图书馆成员中的full_name字段获取。
现在,转到图书馆会员资格列表并创建一个新文档。您将看到图书馆成员字段是一个下拉菜单,显示存在的记录作为选项。选择一个图书馆成员,全名将自动获取。很酷,对吧?
# 链接DocTypes
链接DocTypes是在其他DocTypes中作为链接字段链接的DocTypes。所有DocTypes都可以链接。我们可以根据它们存储的数据类型将DocTypes大致分类为主记录和交易记录。文章、图书馆成员是主记录数据的例子,因为它们代表一个实体(物理的或虚拟的)。图书馆会员资格是存储交易数据的DocType的一个例子。
# 可提交DocTypes
当您在DocType中启用可提交时,它就变成了一个可提交DocType。一个可提交DocType可以有3个状态:草稿、已提交和已取消。处于草稿状态的文档可以像任何文档一样更改,然而一旦它处于已提交状态,文档中任何字段的值都不能更改。一个已提交的文档可以取消,这会使文档无效。如果您注意到,我们的图书馆会员资格DocType中添加了一个额外的字段,称为修改自。这个字段用于跟踪文档中的修改。一旦文档被取消,它只能被修改,这意味着它可以被复制,并且已取消的文档将通过修改自字段链接到新的修改后的文档。
# 会员资格的控制器验证
现在,让我们编写代码,确保每当创建图书馆会员资格时,该成员没有有效的会员资格。
library_membership.py
import frappe
from frappe.model.document import Document
from frappe.model.docstatus import DocStatus
class LibraryMembership(Document):
# 在提交此文档之前进行检查
def before_submit(self):
exists = frappe.db.exists(
"Library Membership",
{
"library_member": self.library_member,
"docstatus": DocStatus.submitted(),
# 检查会员资格的结束日期是否晚于此会员资格的开始日期
"to_date": (">", self.from_date),
},
)
if exists:
frappe.throw("此成员已有有效的会员资格")
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
我们在before_submit方法中编写了逻辑,该方法将在我们提交文档之前运行。我们使用了frappe.db.exists方法来检查是否存在具有我们提供的过滤器的图书馆会员资格记录。如果存在,我们使用了frappe.throw来停止程序的执行,并显示一条消息,让用户知道原因。
现在,尝试创建一个有重叠期限的图书馆会员资格,当您提交文档时应该会出现错误。

# 图书馆交易
让我们创建一个DocType来记录一个拥有有效会员资格的图书馆成员发出或归还文章。
这个DocType将被称为图书馆交易,将包含以下字段:
- 文章 - 链接到文章
- 图书馆成员 - 链接到图书馆成员
- 类型 - 选择,有2个选项:发出和归还
- 日期 - 交易日期
这个DocType也将是一个可提交DocType。

# 交易验证
当发出一篇文章时,我们应该验证图书馆成员是否有有效的会员资格。我们还应该检查文章是否可供发出。让我们编写这些验证的代码。
library_transaction.py
import frappe
from frappe.model.document import Document
from frappe.model.docstatus import DocStatus
class LibraryTransaction(Document):
def before_submit(self):
if self.type == "Issue":
self.validate_issue()
# 设置文章状态为已发出
article = frappe.get_doc("Article", self.article)
article.status = "Issued"
article.save()
elif self.type == "Return":
self.validate_return()
# 设置文章状态为可用
article = frappe.get_doc("Article", self.article)
article.status = "Available"
article.save()
def validate_issue(self):
self.validate_membership()
article = frappe.get_doc("Article", self.article)
# 如果文章已经被发出,则不能发出
if article.status == "Issued":
frappe.throw("文章已被其他成员发出")
def validate_return(self):
article = frappe.get_doc("Article", self.article)
# 如果文章尚未发出,则不能归还
if article.status == "Available":
frappe.throw("文章尚未发出,不能归还")
def validate_membership(self):
# 检查此图书馆成员是否有有效的会员资格
valid_membership = frappe.db.exists(
"Library Membership",
{
"library_member": self.library_member,
"docstatus": DocStatus.submitted(),
"from_date": ("<", self.date),
"to_date": (">", self.date),
},
)
if not valid_membership:
frappe.throw("该成员没有有效的会员资格")
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
这里有大量的代码,但它应该是自解释的。有内联代码注释以供更多解释。
# 图书馆设置
让我们为我们的应用程序创建最后一个DocType:图书馆设置。它将包含以下字段:
- 贷款期限 - 定义贷款期限的天数
- 最大发出文章数 - 限制单个成员可以发出的文章的最大数量
由于我们不需要为这些设置有多个记录,我们将为此DocType启用单一。

创建好DocType后,点击转到图书馆设置,进入表单并设置贷款期限和最大发出文章数的值。
# 单一DocTypes
当一个DocType启用单一时,它就变成了一个单一DocType。单一DocType类似于其他框架中的单例记录。它不会创建新的数据库表。相反,所有单一值都存储在单个表tabSingles中。它通常用于存储全局设置。
# 图书馆设置的验证
让我们对图书馆会员资格进行更改,使得结束日期根据贷款期限和开始日期自动计算。
library_membership.py
import frappe
from frappe.model.document import Document
from frappe.model.docstatus import DocStatus
class LibraryMembership(Document):
# 在提交此文档之前进行检查
def before_submit(self):
exists = frappe.db.exists(
"Library Membership",
{
"library_member": self.library_member,
"docstatus": DocStatus.submitted(),
# 检查会员资格的结束日期是否晚于此会员资格的开始日期
"to_date": (">", self.from_date),
},
)
if exists:
frappe.throw("此成员已有有效的会员资格")
# 获取贷款期限,并通过给from_date添加loan_period来计算to_date
loan_period = frappe.db.get_single_value("Library Settings", "loan_period")
self.to_date = frappe.utils.add_days(self.from_date, loan_period or 30)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
我们使用了frappe.db.get_single_value方法来获取图书馆设置DocType中的loan_period值。
现在,让我们对图书馆交易进行更改,使得在发出文章时检查是否达到了最大限制。
library_transaction.py
import frappe
from frappe.model.document import Document
from frappe.model.docstatus import DocStatus
class LibraryTransaction(Document):
def before_submit(self):
if self.type == "Issue":
self.validate_issue()
self.validate_maximum_limit()
# 设置文章状态为已发出
article = frappe.get_doc("Article", self.article)
article.status = "Issued"
article.save()
elif self.type == "Return":
self.validate_return()
# 设置文章状态为可用
article = frappe.get_doc("Article", self.article)
article.status = "Available"
article.save()
def validate_issue(self):
self.validate_membership()
article = frappe.get_doc("Article", self.article)
# 如果文章已经被发出,则不能发出
if article.status == "Issued":
frappe.throw("文章已被其他成员发出")
def validate_return(self):
article = frappe.get_doc("Article", self.article)
# 如果文章尚未发出,则不能归还
if article.status == "Available":
frappe.throw("文章尚未发出,不能归还")
def validate_maximum_limit(self):
max_articles = frappe.db.get_single_value("Library Settings", "max_articles")
count = frappe.db.count(
"Library Transaction",
{"library_member": self.library_member, "type": "Issue", "docstatus": DocStatus.submitted()},
)
if count >= max_articles:
frappe.throw("发出文章的数量已达到最大限制")
def validate_membership(self):
# 检查此图书馆成员是否有有效的会员资格
valid_membership = frappe.db.exists(
"Library Membership",
{
"library_member": self.library_member,
"docstatus": DocStatus.submitted(),
"from_date": ("<", self.date),
"to_date": (">", self.date),
},
)
if not valid_membership:
frappe.throw("该成员没有有效的会员资格")
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
我们添加了一个validate_maximum_limit方法,并使用了frappe.db.count来计算成员进行的交易数量。
好了,我们已经涵盖了DocType创建的基础知识和DocType的类型。我们还为各种DocTypes编写了业务逻辑。
到目前为止做得很好。让我们继续。
接下来: 表单脚本.md