跳到主要内容

如何使用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.pycar.jstest_car.pycar.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() 方法返回的所有键值有其自身的好处,如自动 modifiedcreation 时间戳。我选择存储以上所有字段,因为它们是框架用来增强 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!