架构

Frappe 框架是一个全栈式的基于 Web 的框架,它包含了将站点部署到生产环境所需的全部
工具。当你设置一个 Frappe 站点时,数据库、缓存、后台
任务、实时通知等都会被自动配置好。

Frappe 框架基于 Python,因此它使用 virtualenv 来为多个 Python 版本设置
隔离的环境。你也可以用它来部署不同 Frappe 版本的站点。

下图大致展示了 frappe-bench 的目录结构
及其与整个技术栈不同部分的交互方式。


架构

要设置一个基于 Frappe 的站点,你首先需要安装 Bench。如果你还没有
安装它,请查看“安装”
页面。

你可以通过运行以下命令来创建一个新的 frappe-bench 环境:

bench init frappe-bench

该命令将执行以下操作:

  1. 创建一个名为 frappe-bench 的目录,并在其中创建 frappe-bench/sitesfrappe-bench/apps 目录。
  2. frappe-bench/env 下设置一个 Python 虚拟环境。
  3. 创建一个 frappe-bench/config 文件夹,用于存放 Redis 配置文件。
  4. 下载 frappe 应用并 pip install 它。
  5. 安装 Node 包。
  6. 构建 JS/CSS 资源文件。

每个 frappe-bench 环境都会启动它自己的 Web、Redis 和 Node 进程。

为什么选择Frappe框架?

Frappe 的诞生是为了驱动我们的旗舰产品 ERPNext
Frappe 的核心理念是尽可能少写代码。我们更倾向于
配置而非代码。如果一个功能是通用的并且我们需要它,我们就会把它直接
集成到框架中。

这使得我们能够用一个小型开发团队构建并维护像 ERP 这样
拥有数千个功能的复杂应用。

快速应用开发

搭建好 Frappe 框架后,你可以迅速进入高效工作状态。创建
模型、编写控制器代码以及更新视图,这些都由框架
自动处理。

功能齐全,开箱即用

现代 Web 应用可能需要的一切,框架中很可能
已经内置了。PDF 生成、邮件、短信、网页……
应有尽有。如果 Frappe 框架中缺少了什么,我们可能
现在就已经在开发它了 :)

易于部署

Bench 是管理 Frappe 所有事务的一体化工具。它负责应用
更新、数据库迁移、生成 nginx 和 supervisor 的配置、
搭建新应用以及更多功能。

多租户支持

Bench 原生支持多租户。这意味着你可以在同一套代码基础上
启动多个应用实例,并各自使用独立的数据库。它还支持
基于端口的多租户模式,你可以为不同版本的应用
设置多个 bench 环境。

可扩展架构

通过创建你自己的应用,可以在 Frappe 之上构建强大的扩展。应用可以
自带模型,也可以修改 Frappe 中现有的模型。

什么是Frappe框架?

Frappe 是一个用 Python 和 Javascript 编写的全栈式 Web 框架,功能全面,开箱即用。

它是驱动 ERPNext 的底层框架。该框架非常通用,可用于构建数据库驱动的应用程序。

元数据驱动

在 Frappe 中,元数据是一等公民。它用于生成数据库表、设计表单以及配置许多功能。元数据存储在一个称为 DocType 的模型中。

我们以名为 ToDo 的 DocType 为例。它将包含诸如 statusdatedescription 之类的字段。

以下是 todo.json 可能的样子:

{
 "name": "ToDo",
 "module": "Desk",
 "field_order": [
  "status",
  "date",
  "description"
 ],
 "fields": [
  {
   "default": "Open",
   "fieldname": "status",
   "fieldtype": "Select",
   "in_global_search": 1,
   "in_list_view": 1,
   "in_standard_filter": 1,
   "label": "Status",
   "options": "Open
Closed"
  },
  {
   "fieldname": "date",
   "fieldtype": "Date",
   "in_standard_filter": 1,
   "label": "Due Date"
  },
  {
   "fieldname": "description",
   "fieldtype": "Text Editor",
   "in_global_search": 1,
   "label": "Description",
   "reqd": 1
  }
 ]
}

这样的配置将生成一个数据库表,其表结构可能如下所示:

MariaDB [_baa0f26509a564b6]> desc tabToDo;
+-----------------------+--------------+------+-----+---------+-------+
| Field                 | Type         | Null | Key | Default | Extra |
+-----------------------+--------------+------+-----+---------+-------+
| name                  | varchar(140) | NO   | PRI | NULL    |       |
| creation              | datetime(6)  | YES  |     | NULL    |       |
| modified              | datetime(6)  | YES  | MUL | NULL    |       |
| modified_by           | varchar(140) | YES  |     | NULL    |       |
| owner                 | varchar(140) | YES  |     | NULL    |       |
| docstatus             | int(1)       | NO   |     | 0       |       |
| idx                   | int(8)       | NO   |     | 0       |       |
| status                | varchar(140) | YES  |     | Open    |       |
| description           | longtext     | YES  |     | NULL    |       |
| date                  | date         | YES  |     | NULL    |       |
+-----------------------+--------------+------+-----+---------+-------+

丰富的管理界面

Frappe 不仅管理后端,还附带了一个功能丰富的管理界面,称为 Desk。当您在 Frappe 中创建 DocType 时,系统会为其生成多个视图。以下是其中一些视图:

列表视图支持分页、筛选、排序和批量编辑记录。

列表视图

用于编辑记录的表单视图还支持文件附件、PDF 格式、评论、电子邮件等功能。

表单视图

报表生成器支持添加列、分组、筛选、排序,并将其保存为配置。

报表生成器

用户、角色和权限

Frappe 内置了用户和角色管理功能。用户是指可以登录系统并执行授权操作(如创建、更新或删除记录)的人。角色是对 DocType 及其允许执行的操作的映射。

Python、JS 和 MariaDB

Frappe 框架使用 Python 作为后端。它附带了一个简单而强大的 ORM,作为对 CRUD 操作的抽象。默认数据库是 MariaDB。Postgres 支持目前处于测试阶段。

doc = frappe.new_doc('ToDo')
doc.description = 'Buy Eggs'
doc.insert()

前端是使用 Javascript(jQuery)构建的单页应用程序(SPA)。

实时功能

Frappe 还支持使用 NodeJS 和 socketio 实现实时发布/订阅事件。

# server
frappe.publish_realtime('update_progress', {
    'progress': 42,
    'total': 100
})

# client
frappe.realtime.on('update_progress', (data) => {
    console.log(data)
});

后台任务

Frappe 还支持基于 Python RQ 的后台任务队列。

frappe.enqueue('frappe.job.run_job', arg1='Test', arg2='Test2')

电子邮件

Frappe 支持发送和接收电子邮件,这些邮件也可以关联到单个文档。

打印

Frappe 支持基于 Jinja 模板生成 PDF 打印格式。它还附带了一个拖放式的打印格式生成器。

打印预览

打印格式生成器

功能全面,开箱即用

Frappe 拥有构建现代复杂应用所必需的众多功能。这里仅介绍了基本功能。本指南的其余部分将更详细地介绍这些功能以及其他高级功能,请务必通读全文。

接下来是什么?

本教程的目标是介绍框架的各个组成部分。
我们在这里只是浅尝辄止。Frappe 框架拥有
更多功能,使我们能够构建诸如
ERPNext 之类的复杂软件。

您可以通过阅读其余文档来深入了解各个主题。
以下是一些入门主题供您参考:

  1. 架构
  2. DocType(文档类型)
  3. 文档 API
  4. 表单 API
  5. Bench 命令行工具

需要帮助?

您可以在我们的公共论坛上寻求帮助:discuss.frappe.io

表单脚本

表单脚本是客户端 JavaScript 代码,用于增强表单的用户体验。

假设你想为会员创建一条会员资格记录。为此,你需要
前往“图书馆会员”列表,创建一个新表单,选择会员并填写
其他字段,然后保存。

同样,当你想针对某位会员创建一笔交易时,你需要
新建一个“图书馆交易”表单。

我们可以让这个过程变得更简单。在 library_member.js 中编写以下代码:

frappe.ui.form.on('Library Member', {
 refresh: function(frm) {
 frm.add_custom_button('Create Membership', () => {
 frappe.new_doc('Library Membership', {
 library_member: frm.doc.name
 })
 })
 frm.add_custom_button('Create Transaction', () => {
 frappe.new_doc('Library Transaction', {
 library_member: frm.doc.name
 })
 })
 }
});

现在,刷新页面并前往“图书馆会员”表单。你应该会在右上角看到两个按钮。
点击它们试试看。它们会自动在这些文档中设置
图书馆会员,从而简化操作流程。

这里我们只是浅尝辄止。使用表单脚本,你还可以做更多事情。
在“表单脚本 API”中了解更多关于 API 的信息。

下一步:门户页面

单据类型

Single DocType is a special type of DocType that stores only one record. It is useful for storing settings or configuration values that are global to the application. The data is stored in a single row in the database, and the form is always available for editing.

Library Settings Controller

Now, let’s write code to use the Library Settings in our validations. We will
update the Library Transaction validation to check the loan period and
maximum number of issued articles.

library_transaction.py

from frappe.model.document import Document
from frappe.model.docstatus import DocStatus

import frappe

class LibraryMembership(Document):
    # check before submitting this document
    def before_submit(self):
        exists = frappe.db.exists(
            "Library Membership",
            {
                "library_member": self.library_member,
                "docstatus": DocStatus.submitted(),
                # check if the membership's end date is later than this membership's start date
                "to_date": (">", self.from_date),
            },
        )
        if exists:
            frappe.throw("There is an active membership for this member")

        # get loan period and compute to_date by adding loan_period to from_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)

We used the tabSingles method to get the Library Settings record. Since it
is a Single DocType, we can directly access its fields. We also used the
frappe.db.get_single_value method to count the number of issued articles for the member.

Library Membership Validation Update

We also need to update the Library Membership validation to use the Library
Settings for the loan period.

library_membership.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()
            # set the article status to be Issued
            article = frappe.get_doc("Article", self.article)
            article.status = "Issued"
            article.save()

        elif self.type == "Return":
            self.validate_return()
            # set the article status to be Available
            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)
        # article cannot be issued if it is already issued
        if article.status == "Issued":
            frappe.throw("Article is already issued by another member")

    def validate_return(self):
        article = frappe.get_doc("Article", self.article)
        # article cannot be returned if it is not issued first
        if article.status == "Available":
            frappe.throw("Article cannot be returned without being issued first")

    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("Maximum limit reached for issuing articles")

    def validate_membership(self):
        # check if a valid membership exist for this library member
        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("The member does not have a valid membership")

Now, when you create a Library Membership, the loan period will be automatically
set based on the Library Settings.

Summary

In this tutorial, we learned about different types of doctypes in the framework:

  • Master and Transactional DocTypes
  • Submittable DocTypes
  • Single DocTypes

We also learned how to use Link fields to connect records, fetch values from
linked records, and write validations for our doctypes.

单一文档类型类似于其他框架中的单例记录。它不会
创建新的数据库表。相反,所有单一值都存储在一个名为
tabSingles 的表中。它通常用于存储全局设置。

库设置的验证

让我们对“库会员”进行修改,使得“截止日期”自动
根据“借阅期限”和“起始日期”计算得出。

library_membership.py

from frappe.model.document import Document
from frappe.model.docstatus import DocStatus

import frappe

class LibraryMembership(Document):
    # check before submitting this document
    def before_submit(self):
        exists = frappe.db.exists(
            "Library Membership",
            {
                "library_member": self.library_member,
                "docstatus": DocStatus.submitted(),
                # check if the membership's end date is later than this membership's start date
                "to_date": (">", self.from_date),
            },
        )
        if exists:
            frappe.throw("There is an active membership for this member")

        # get loan period and compute to_date by adding loan_period to from_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)

我们使用了 frappe.db.get_single_value 方法来获取
“库设置”文档类型中的 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()
            # set the article status to be Issued
            article = frappe.get_doc("Article", self.article)
            article.status = "Issued"
            article.save()

        elif self.type == "Return":
            self.validate_return()
            # set the article status to be Available
            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)
        # article cannot be issued if it is already issued
        if article.status == "Issued":
            frappe.throw("Article is already issued by another member")

    def validate_return(self):
        article = frappe.get_doc("Article", self.article)
        # article cannot be returned if it is not issued first
        if article.status == "Available":
            frappe.throw("Article cannot be returned without being issued first")

    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("Maximum limit reached for issuing articles")

    def validate_membership(self):
        # check if a valid membership exist for this library member
        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("The member does not have a valid membership")

我们添加了一个 validate_maximum_limit 方法,并使用 frappe.db.count 来统计
该会员进行的交易次数。

至此,我们已经涵盖了文档类型创建的基础知识和文档类型的种类。
我们还为各种文档类型编写了业务逻辑。

干得好,坚持到了这里。让我们继续前进。

下一步:表单脚本

控制器方法

控制器方法允许您在文档的生命周期内编写业务逻辑。

让我们创建第二个文档类型:图书馆会员。它将包含以下字段:

  1. 名字(数据,必填)
  2. 姓氏(数据)
  3. 全名(数据,只读)
  4. 电子邮件地址(数据)
  5. 电话(数据)

创建文档类型后,前往图书馆会员列表,从 设置 > 重新加载 清除缓存,然后创建一个新的图书馆会员。

如果您注意到,表单中没有显示“全名”字段。这是因为我们将其设置为只读。只有当它有值时才会显示。

让我们在 Python 控制器类中编写代码,使“全名”能够根据“名字”和“姓氏”自动计算。

打开您的代码编辑器,打开文件 library_member.py 并进行以下更改:

librarymember.py

class LibraryMember(Document):
    #this method will run every time a document is saved
    def before_save(self):
        self.full_name = f'{self.first_name} {self.last_name or ""}'

注意

如果上述代码片段对您不起作用,请确保已启用服务器端脚本,然后重新启动 bench

bench set-config -g server_script_enabled true

我们在 before_save 方法中编写了逻辑,该方法在每次保存文档时运行。这是 Document 类提供的众多钩子之一。您可以在控制器文档中了解所有可用钩子的更多信息。

现在,返回并创建另一个图书馆会员,保存后即可看到“全名”显示出来。

下一步:DocType 的类型

单据类型特性

在上一章中,我们创建了 Article 文档类型。让我们看看还有哪些功能可以自定义。

命名

如果你使用表单创建了文档,你可能会注意到文档的 name 值是一个随机生成的哈希值。让我们做一个更改,使我们提供的 Article Name 成为文档的 name

为此,请从搜索栏打开文档类型列表,然后点击 Article。现在,向下滚动到 Naming 部分,在 Auto Name 字段中输入 field:article_name。点击 Save。

现在,返回 Article 列表并再次创建一篇新文章。

现在,文档的 name 将是 Article Name,并且它在所有文章中必须是唯一的。因此,你不能创建另一篇具有相同名称的文章。

你也可以通过在 mariadb 控制台中运行 select 查询来检查数据库记录。

MariaDB [_ad03fa1a016ca1c4]> select * from tabArticle;

| ------------ | ---------------------------- | ---------------------------- | --------------- | --------------- | ----------- | -------- | ------- |
| name | creation | modified | modified_by | owner | docstatus | parent | parent
| ------------ | ---------------------------- | ---------------------------- | --------------- | --------------- | ----------- | -------- | ------- |
| bd514646b9 | 2020-10-10 16:24:43.033457 | 2020-10-10 16:24:43.033457 | Administrator | Administrator | 0 | NULL | NULL
| Catch 22 | 2020-10-10 16:41:49.734499 | 2020-10-10 16:41:49.734499 | Administrator | Administrator | 0 | NULL | NULL
| ------------ | ---------------------------- | ---------------------------- | --------------- | --------------- | ----------- | -------- | ------- |

了解更多关于 DocType 命名的不同类型。

表单布局

让我们自定义表单中字段的布局方式,同时充分利用可用空间。转到 Article 文档类型,滚动到 Fields 部分,并添加两个新字段,类型分别为 Column BreakSection Break。我们还将隐藏 Image 字段,因为它不需要在表单中显示。查看 GIF 以了解具体操作。

表单设置

转到 Article 文档类型并向下滚动到 Form Settings 部分。在 Image Field 字段中输入 image。这将在表单的左上角显示图像。你还可以启用 Allow Rename 以允许重命名文档。

权限

你还可以配置要为 DocType 允许哪些角色,以及要限制哪些操作。转到 Article 文档类型,向下滚动到 Permission Rules 部分,并添加角色。

你还可以配置允许特定角色执行的操作类型。让我们添加一个 Librarian 角色,该角色拥有所有操作的权限,以及一个 Library Member 角色,该角色拥有 Read 操作的权限。

你可以通过创建一个具有 Librarian 角色的新用户,以及另一个具有 Library Member 角色的新用户来测试这一点。分别使用每个用户登录,查看允许哪些操作。

下一步:控制器方法

创建文档类型

DocType 类似于其他框架中的 Model(模型)。除了定义属性外,它还定义了模型的行为。

启用开发者模式

在创建 DocType 之前,我们需要在 bench 上启用开发者模式。这将允许我们在创建 DocType 时生成样板代码,并且我们可以通过应用将这些代码纳入版本控制。

打开终端,如果 bench 服务器已经在运行,请先停止它,然后从 frappe-bench 目录运行以下命令:

bench set-config -g developer_mode true
bench start

创建 DocType

在 Desk 中,使用 Awesomebar 导航到 DocType 列表。此列表将包含框架自带的 DocType、已安装的 Frappe 应用中的 DocType,以及您可以为每个站点创建的自定义 DocType。

我们要创建的第一个 DocType 是 Article。要创建它,请点击“新建”。

  1. 在“名称”字段中输入 Article
  2. 在“模块”字段中选择 Library Management
  3. 在“字段”表中添加以下字段:
  4. Article Name(数据,必填)
  5. Image(附件图像)
  6. Author(数据)
  7. Description(文本编辑器)
  8. ISBN(数据)
  9. Status(选择)- 输入两个选项:Issued 和 Available(输入 Issued,按回车,然后输入 Available)
  10. Publisher(数据)

请参考以下 GIF 了解具体操作步骤:

添加完字段后,点击“保存”。

您会在表单右上角看到一个 前往 Article 列表 按钮。点击它进入 Article 列表。这里您会看到一个空列表,因为表中还没有记录。

让我们创建一些记录。但在此之前,我们需要清除 Desk 缓存。点击导航栏右侧的 设置 下拉菜单,然后点击 重新加载

现在,您应该能看到 新建 按钮。点击它,您将看到 Article DocType 的表单视图。填写表单并点击“保存”。您就创建了第一个 Article 文档。返回列表视图,您应该能看到一条记录。

创建 Article DocType 时发生了什么?

1. 数据库表

系统创建了一个名为 tabArticle 的数据库表,其中包含我们在字段表中指定的字段。您可以通过 MariaDB 控制台检查来确认这一点。

bench --site library.localhost mariadb
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 2445938
Server version: 10.4.13-MariaDB Homebrew

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [_ad03fa1a016ca1c4]> desc tabArticle;
+--------------+--------------+------+-----+-----------+-------+
| Field        | Type         | Null | Key | Default   | Extra |
+--------------+--------------+------+-----+-----------+-------+
| name         | varchar(140) | NO   | PRI | NULL      |       |
| creation     | datetime(6)  | YES  |     | NULL      |       |
| modified     | datetime(6)  | YES  | MUL | NULL      |       |
| modified_by  | varchar(140) | YES  |     | NULL      |       |
| owner        | varchar(140) | YES  |     | NULL      |       |
| docstatus    | int(1)       | NO   |     | 0         |       |
| parent       | varchar(140) | YES  | MUL | NULL      |       |
| parentfield  | varchar(140) | YES  |     | NULL      |       |
| parenttype   | varchar(140) | YES  |     | NULL      |       |
| idx          | int(8)       | NO   |     | 0         |       |
| article_name | varchar(140) | YES  |     | NULL      |       |
| image        | text         | YES  |     | NULL      |       |
| author       | varchar(140) | YES  |     | NULL      |       |
| description  | longtext     | YES  |     | NULL      |       |
| isbn         | varchar(140) | YES  |     | NULL      |       |
| status       | varchar(140) | YES  |     | Available |       |
| publisher    | varchar(140) | YES  |     | NULL      |       |
| _user_tags   | text         | YES  |     | NULL      |       |
| _comments    | text         | YES  |     | NULL      |       |
| _assign      | text         | YES  |     | NULL      |       |
| _liked_by    | text         | YES  |     | NULL      |       |
+--------------+--------------+------+-----+-----------+-------+
21 rows in set (0.002 sec)

MariaDB [_ad03fa1a016ca1c4]>

我们以标题格式(Title Case)指定的字段会自动转换为小写蛇形命名(snake case),并用作表中的列名。例如,article_nameimageauthordescription

此外,还创建了许多其他字段,如 namecreationmodifiedmodified_by。这些是所有 DocType 都会创建的标准字段。name 是主键列。

如果您通过表单创建了一条记录,您也可以运行标准的 SELECT 查询来获取这些行。

MariaDB [_ad03fa1a016ca1c4]> select * from tabArticle;
+------------+----------------------------+----------------------------+---------------+---------------+-----------+--------+-------------+------------+-----+-----------------------------+--
| name       | creation                   | modified                   | modified_by   | owner         | docstatus | parent | parentfield | parenttype | idx | article_name                | i
+------------+----------------------------+----------------------------+---------------+---------------+-----------+--------+-------------+------------+-----+-----------------------------+--
| bd514646b9 | 2020-10-10 16:24:43.033457 | 2020-10-10 16:24:43.033457 | Administrator | Administrator |         0 | NULL   | NULL        | NULL       |   0 | The Girl with all the Gifts | N
+------------+----------------------------+----------------------------+---------------+---------------+-----------+--------+-------------+------------+-----+-----------------------------+--

MariaDB [_ad03fa1a016ca1c4]>

2. Desk 视图

系统还为我们的 DocType 创建了许多视图。Article 列表是显示数据库表记录的列表视图。表单视图是您创建新文档或查看现有文档时显示的视图。

3. 表单布局

如果您留意,表单中的字段布局是按照您在字段表中的排序方式排列的。例如,Article Name 是第一个字段,其次是 Image,然后是 Author。在教程的后续部分,我们将学习如何进一步自定义此布局。

4. 样板代码


注意

请确保在 DocType 配置中取消勾选“自定义?”复选框。否则,下面讨论的文件将不会生成。此处有相关解释。


如果您查看应用中的更改,应该会发现创建了许多文件。打开终端,从 frappe-bench 目录运行以下命令。

$ cd apps/library_management
$ git status -u
On branch master
Untracked files:
  (use "git add 
<file>..." to include in what will be committed)
    library_management/library_management/doctype/__init__.py
    library_management/library_management/doctype/article/__init__.py
    library_management/library_management/doctype/article/article.js
    library_management/library_management/doctype/article/article.json
    library_management/library_management/doctype/article/article.py
    library_management/library_management/doctype/article/test_article.py

nothing added to commit but untracked files present (use "git add" to track)
</file>

article.json – 定义 DocType 属性的 JSON 文件

article.js – 表单视图的客户端控制器

article.py – Article 的 Python 控制器

test_article.py – 用于编写测试的 Python 单元测试样板文件

如您所见,DocType 描述了模型的很多信息。它不仅定义了表和列名,还定义了它在 Desk 中各种视图中的呈现方式。

到目前为止,您很好地跟上了教程。让我们继续!

下一步:DocType 功能

创建站点

每个站点都附带一个数据库。您可以通过特定于站点的脚本对其进行自定义,或者在其上安装应用。

创建新站点

要创建新站点,请在 frappe-bench 目录下运行以下命令:

$ bench new-site library.localhost
MySQL root password:

Installing frappe...
Updating DocTypes for frappe        : [========================================] 100%
Set Administrator password:
*** Scheduler is disabled ***
Current Site set to library.localhost

此命令将创建一个新数据库,因此您需要输入 MariaDB 的 root 密码。它还会要求您为管理员用户设置密码,请设置一个您不会忘记的密码。这将在以后派上用场。

现在,您将在 sites 目录下拥有一个名为 library.localhost 的新文件夹。

如果站点创建因任何原因失败,系统会提示您回滚更改。这将删除您站点目录中的 library.localhost 或等效文件,并删除已创建的数据库/用户。这样您就可以轻松重试,而无需手动清理不可用的站点。

站点目录结构

站点目录结构大致如下:

sites/library.localhost
├── indexes
│   └── web_routes
├── locks
├── logs
├── private
│   ├── backups
│   └── files
├── public
│   └── files
└── site_config.json

indexes 文件夹包含通过网站搜索生成的索引。

locks 文件夹维护站点内文档的基于文件的锁,以及站点自身状态的指示器。

如您所见,private 文件夹将包含所有数据库备份和私有文件。私有文件是需要身份验证才能访问的用户上传文件。

public 文件夹将包含无需身份验证即可访问的文件。其中可以包含无需登录即可访问的网站图片。

site_config.json 文件包含特定于此站点且不应进行版本控制的配置。这类似于环境变量文件。如果您查看该文件的内容,会发现其中包含此站点的数据库配置值。

{
 "db_name": "_ad03fa1a016ca1c4",
 "db_password": "pz1d2gN5y35ydRO5",
 "db_type": "mariadb"
}

在浏览器中访问站点

bench 允许您创建多个站点,并在浏览器中通过同一端口分别访问它们。这就是我们在 bench 中所说的多租户支持。

Frappe 将通过将请求的主机名与站点名称进行匹配来确定要提供哪个站点,因此您应该能够通过 http://library.localhost:8000 访问您的站点。

如果您的站点名称不以 .localhost 结尾,则此方法无法直接为您工作,因为您必须告诉操作系统 site_name 应指向 localhost。为此,您可以将以下条目添加到您的 /etc/hosts 文件中。

127.0.0.1 site_name

这会将 library.localhost 映射到 localhost。Bench 有一个便捷命令可以完成此操作。

$ bench --site site_name add-to-hosts

这将要求您输入 root 密码,并向您的 /etc/hosts 文件添加一个条目。

太棒了,现在您可以在 http://library.localhost:8000 访问您的站点。恭喜您走到了这一步。

在站点上安装应用

要在我们的站点上安装我们的图书馆管理应用,请运行以下命令:

$ bench --site library.localhost install-app library_management

Installing library_management...

要确认应用是否已安装,请运行以下命令:

$ bench --site library.localhost list-apps
frappe
library_management

您应该会看到 frappelibrary_management 已作为已安装的应用出现在您的站点上。

当您创建新站点时,frappe 应用会默认安装。

登录 Desk

在继续以下操作之前,请确保站点处于开发者模式。为确保这一点,请在您的站点中运行以下命令

bench set-config -g developer_mode 1

然后使用 bench start 命令重启 bench

要在我们的应用中创建 DocType,我们必须登录 Desk。转到 http://library.localhost:8000,它应该会向您显示一个登录页面。

输入 Administrator 作为用户,并输入您在创建站点时设置的密码。

成功登录后,您将看到设置向导。这是一次性的设置向导,用于为您的站点设置本地化详细信息。请继续,选择您的语言,并完成向导。

干得好,您已经走到了这一步!

您应该会看到类似这样的 Desk:

站点命令

我们使用 --site 选项运行了几个 bench 命令。这些命令被称为站点命令。

以下是一些有用的站点命令。

Python 控制台

# access the python console
$ bench --site library.localhost console
Apps in this namespace:
frappe, library_management

In [1]:

MariaDB 控制台

# access the mariadb console
$ bench --site library.localhost mariadb
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 2333498
Server version: 10.4.13-MariaDB Homebrew

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [_ad03fa1a016ca1c4]>

数据库备份

$ bench --site library.localhost backup
Backup Summary for library.localhost at 2020-10-06 23:21:17.277960
Config  : ./library.localhost/private/backups/20201006_232116-library_test-site_config_backup.json   94.0B
Database: ./library.localhost/private/backups/20201006_232116-library_test-database.sql.gz           217.4KiB
Backup for Site library.localhost has been successfully completed

您可以通过运行以下命令查看所有站点命令的列表:

$ bench --help
Usage: bench frappe [OPTIONS] COMMAND [ARGS]...

Options:
  --site TEXT
  --profile Profile
  --verbose Verbose
  --force Force
  --help Show this message and exit.

Commands:
  add-system-manager          Add a new system manager to a site
  add-to-email-queue          Add an email to the Email Queue
  add-to-hosts                Add site to hosts
  backup                      Backup
  browse                      Opens the site on web browser
  build                       Minify + concatenate JS and CSS files, build...
  build-message-files         Build message files for translation
  build-search-index
  bulk-rename                 Rename multiple records via CSV file
  clear-cache                 Clear cache, doctype cache and defaults
  clear-website-cache         Clear website cache
  console                     Start ipython console for a site
  data-import                 Import documents in bulk from CSV or XLSX...
  destroy-all-sessions        Clear sessions of all users (logs them out)

下一步:创建 DocType