跳到主要内容

如何使用MongoDB 作为数据库存储

· 阅读需 12 分钟

https://frappe.io/blog/engineering/mongodb-powered-doctypes

在本教程中,我们将学习如何使用 MongoDB 作为 DocType 的数据源,使用 Virtual DocType 的强大功能。

2022年11月27日 · 7分钟


先决条件​

本教程假设您了解 Frappe 框架和 MongoDB 的基础知识。

正常的DocTypes​

Frappe的Doctype类似于 Django 或类似框架中的模型。当您创建一个新的 DocType 时,在 MariaDB 数据库中创建一个新的数据库表,并且 Frappe 为您管理表和 CRUD 操作。从Desk创建 DocType 时,将在后台在相应的数据库表中插入一个新行。更新或删除文档时也是如此,Framework 负责处理数据库操作。因此,我们可以说,DocType 的后端基本上是 MariaDB (SQL)数据库中的一个表。

但是,如果我们希望 DocType 由其他数据源驱动,该怎么办呢?如果我们不希望为Mariadb创建和管理数据库表的默认行为,该怎么办?在这种情况下,我们希望控制 DocType 的数据源。这就是Virtual DocTypes发挥作用的地方。

Virtual DocTypes​

Virtual DocTypes是在 Frappe 框架的13版中引入的。基本上,它使您能够控制 DocType 的完整“后端”。与普通 DocType 不同,VirtualDocType 在站点数据库中没有相应的表。作为开发人员,您必须编写代码来控制 DocType 的数据源。数据可以存在于任何地方: 一个 CSV 文件,一个基于文件的数据库(如 SQLite) ,一个成熟的数据库(如 Cassandra 或 MongoDB)。在本教程中,我们将学习如何使用 MongoDB 作为 DocType 的后端。

设置 MongoDB​

在本教程中,我将使用一个永远免费的 MongoDB Atlas 实例(云上的 MongoDB) here, 。您可以旋转一个本地实例,或者在这里创建一个空闲实例,以便跟进。

一旦你有一个工作的 MongoDB 数据库服务器,复制连接字符串,我们准备好了!

Installing PyMongo 安装 PyMongo​

PyMongo 是用 Python 连接和处理 MongoDB 数据库的官方 Python 驱动程序。

让我们使用以下命令在 Frappe Bench 上安装 PyMongo Python 包:

bench pip install "pymongo[srv]"

安装完成后,我们可以打开站点的 Python 控制台,并通过连接到 MongoDB 实例来测试驱动程序。

我们可以使用以下命令打开站点控制台:

bench --site <site_name> console

让我们连接到数据库并插入一些测试记录。我们将在 MongoDB 集合中存储有关汽车的信息。我们将数据库命名为 cars-database ,集合可以命名为 cars 。

from pymongo import MongoClient

CONNECTION_STRING = "<your-connection-string>"

client = MongoClient(CONNECTION_STRING)
db = client['cars-database'] # Get the database
cars = db['cars'] # Get the collection

# Insert some test records
cars.insert_many([
{"make": "BMW", "model": "M4", "year": 2019, "name": "ccdeb89695"},
{"make": "Renault", "model": "Kwid", "year": 2022, "name": "79c1eb5a4b"}
])

如果正确地设置了数据库,并且 PyMongo 安装成功,那么上面的代码应该在没有任何错误的情况下执行。

创建Virtual DocType​

转到 DocType 列表并创建一个新的 DocType。让我们将其命名为 Car ,并将模块设置为我们的自定义应用程序(在我的例子中为 My Awesome App )。选中 DocType 表单顶部附近的“ Is Virtual”复选框,使其成为 Virtual DocType:

DocType Form View

其他的东西类似于任何普通的 DocType。我们将为 Car 文档类型创建3个字段,如下所示:

  1. Make - Data - Mandatory 强制性资料规定
  2. Model - Data - Mandatory 模型-资料-强制性
  3. Year - Int - Non-Negative 年-国际-非负数

按下 Save 并导航到 Car 列表。正如预期的那样,该列表为空:

Empty Car List View

您将无法创建新文档,因为该功能不存在。我们需要编写代码来填充列表视图、创建新文档等等。好戏开始了!

The Generated Boilerplate Code 生成的样板代码​

在您选择的代码编辑器中打开您的自定义应用程序,并为 Car DocType 查找新生成的文件。与任何普通 DocType 一样,生成4个文件: car.py 、 car.js 、 test_car.py 和 car.json 。

打开 car.py 文件。您会注意到,已经为您生成了一些用于实现 Virtual DocType Car 的样板代码:

class Car(Document):
def db_insert(self):
pass

def load_from_db(self):
pass

def db_update(self):
pass

@staticmethod
def get_list(args):
pass

@staticmethod
def get_count(args):
pass

@staticmethod
def get_stats(args):
pass

这些方法必须存在于 VirtualDocType 的控制器类中。让我们从让列表视图工作开始。总的思想是从 MongoDB cars 集合中获取所有的汽车,并从 get_list 静态方法返回它:

class Car(Document):
@staticmethod
def get_list(args):
cars = get_cars_collection()
cars_list = []

for car in cars.find():
cars_list.append({
**car,
"_id": str(car["_id"])
})

return cars_list

...

# Utility function to get the db collection
def get_cars_collection():
client = MongoClient(CONNECTION_STRING)
db = client['cars-database']
cars = db.cars
return cars

在这里,我们将获取所有的汽车,并将它们作为 Python 字典的列表返回。请注意在返回之前如何将 _id 字段转换为 str 。之所以需要这样做,是因为 _id 字段的类型为 bson.ObjectId ,这不是可序列化的 JSON。

让我们回到列表视图并刷新:

Working List View

瞧! 列表视图正在从 MongoDB 集合中获取数据!

尝试打开任何文档都会抛出一个错误,因为我们还没有编写代码来处理它。

Getting a particular document 获取特定的文档​

我们需要实现以填充表单视图的方法是 load_from_db :

def load_from_db(self):
cars = get_cars_collection()
d = cars.find_one({"name": self.name})
super(Document, self).__init__(d)

这里我们使用 doctype 的 name 字段从我们的 cars 集合中获取特定的文档。如果您还记得,我们在测试 PyMongo 客户机时也在插入的测试记录中添加了 name 字段。我们将使用 name 作为主键,而不使用 MongoDB 生成的 _id 字段。 name 字段将由 Frappe Framework 为我们自动生成,我们将在下一节中看到。

现在可以打开 Car 列表视图并尝试打开单个文档。

Creating a new document 创建一个新文档​

现在,让我们实现为 Car DocType 创建一个新文档的功能:

def db_insert(self, *args, **kwargs):
cars = get_cars_collection()
d = self.get_valid_dict()
cars.insert_one(d)

get_valid_dict 方法返回文档的验证值字典。让我们检查其内容:

{'creation': '2022-11-27 13:28:48.065820',
'docstatus': 0,
'idx': 0,
'make': 'Honda',
'model': 'Dezire',
'modified': '2022-11-27 13:28:48.065820',
'modified_by': 'Administrator',
'name': 'c1f463ae7b',
'owner': 'Administrator',
'year': 2022}

这里我们有各种各样的字段以及我们创建的字段(make、 model 和 year)。您可以选择只在 MongoDB 实例中存储所需的字段,也可以存储以上所有字段。存储从 get_valid_dict() 方法返回的所有键值有其自身的好处,如自动 modified 和 creation 时间戳。我选择存储以上所有字段,因为它们是框架用来增强 doctype 的有用元数据。

现在让我们尝试创建一个新的 Car :

New Car Document

填写详细信息并点击保存。您应该能够在 MongoDB Atlas Collection 仪表板中看到新创建的汽车:

Newly Added Record in MongoDB Atlas Dashboard

Updating a document 更新文档​

我们将需要实现 db_update 方法以使更新操作起作用。它也相当直截了当:

def db_update(self):
cars = get_cars_collection()
updated_values = self.get_valid_dict()
cars.update_one({"name": self.name}, {"$set": updated_values})

这里我们再次使用 get_valid_dict 方法来获得所需的键-值对。现在可以尝试编辑文档并保存更改。现在应该在 MongoDB 中同步更改了!

Deleting a document 删除文档​

为了使删除文档功能正常工作,我们将实现如下所示的 delete 方法:

def delete(self):
cars = get_cars_collection()
cars.delete_one({"name": self.name})

文件计数​

列表视图显示文档的总数。我们可以通过从 get_count 静态方法返回该数字来提供该数字:

@staticmethod
def get_count(args):
return get_cars_collection().count_documents({})

Additional Resources 其他资源​

您可以在这里找到 Car DocType 控制器代码的完整源代码。我还添加了一些示例代码,以使一些列表视图过滤器也能工作。

虚拟文档类型的文档是一个很好的起点。它包含了一个使用 JSON 文件作为 doctype 后端的非常好的例子。

官方的 PyMongo 文档是查找使用 PyMongo 驱动程序示例的好地方。

根据 Rushabh 在评论中提出的关于通用基类的想法,我实现了一个简单的抽象基类,它可以轻松地将任何 DocType 控制器(Virtual DocType)转换为 MongoDB 驱动的文档。你可以在这里找到密码。

使用基类,只需实现一个方法即可:

# Copyright (c) 2022, Hussain and contributors
# For license information, please see license.txt


from my_awesome_app.my_awesome_app.doctype.car.car import MongoDBDocument, get_mongodb_client




class Person(MongoDBDocument):
@staticmethod
def get_collection():
client = get_mongodb_client()
db = client['persons-database']
persons = db.persons
return persons

In the end 最后​

一旦我们完成了实现,像 frappe.get_doc 和 DocTypeRESTAPI 这样的东西就可以开箱即用了!是不是很神奇!

如您所见,将 MongoDB 用作 DocType 的数据源是多么容易。您可以遵循相同的模式来连接任何其他数据库,如 Cassandra 或甚至 FirebaseFirestore。这使得 DocType 更加强大。继续,创建由您最喜欢的数据库支持的 DocType!