跳到主要内容

· 阅读需 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!

· 阅读需 2 分钟
林贵南

背景

我相信很多人都跟我类似, 对于ERPNext是否能集成企业微信感兴趣。

下面这个地址提供了一种实现的方式。

https://github.com/saoxia/erpnext_china/blob/develop/.github/doc/%E4%BC%81%E4%B8%9A%E5%BE%AE%E4%BF%A1%E7%99%BB%E5%BD%95%E9%85%8D%E7%BD%AE%E8%AF%B4%E6%98%8E.md

我也是基于如上的内容做了相关的定制和实现, 但是实现过程做了一些微调。 下面是具体实现的效果:

网页登录集成:

ERPNext的用户的用户名 与 企业微信的后台 的账号 是一样的。 大小写不一样关系不大。

社交登录密钥的设定:

企业微信后台的设定:

登录效果:

在企业微信内部直接打开ERPNext

方法来自于群里头的restart的分享: https://gitee.com/sonic3k/wxwork_en/

对企业微信后台做配置主页:

具体的替换串为如下,其中xxx,yyy,zzz 替换成真实的数据 https://open.weixin.qq.com/connect/oauth2/authorize?appid=xxx&redirect_uri=yyy&response_type=code&scope=snsapi_base&state=STATE&agentid=zzz#wechat_redirect

最后实现的效果: 在PC端的界面上, 可以直接打开网页, 而无须登录。

· 阅读需 6 分钟
Jurin Liyun

对于那些刚接触 ERPNext 的人来说,ERPNext 是由印度软件公司 Frappe Technologies Pvt.Ltd. 开发的一个 free and open-source 的集成企业资源规划Enterprise resource planning (ERP)软件[2][3] . 它基于 MariaDB 数据库系统,使用基于 Python 的服务器端框架[4] (source: wikipedia)

你可以在他们的网站上了解更多有关 ERPNext 的详情。在这篇文章中,我将试图让你了解Frappe已经提供给我们关于他们的 ERP 系统的定制。

首先,我想给出我的场景,为什么我的团队需要定制核心。

在我工作的一个组织中,他们需要定制的工作流程来验证一个包含多个用户角色的用户,并且在这个角色中,并不是所有的用户都能以相同的批准价值来批准,即使他们被分配了相同的角色。相反,在同一个角色中,管理员可以设置他们希望允许批准的多个授权规则。

在 ERPNext 中,有两种内置的方法可以实现这一点,即,

  1. Authorization Rule 授权规则

    授权文档类型只允许组织设置授权销售的角色和用户。

img

  1. 工作流程

在工作流中,我们只能指定文档流在用户创建时的行为,直到销售批准为止。*

img

状态

img

规则

img

规则细节。条件支持

对于许多组织来说,内置特性已经足够好了。但是,当涉及到解决某些企业组织的复杂问题时,它总是令我着迷。导致这些复杂性的原因之一,是我一直发现的管理政策的遗留问题。

我的委托人要求我的团队再做一次验证。系统应该能够满足包含角色的人员授权规则。在 ERPNext 中,我们有角色管理器,在每个角色中都包括许多人。让我为你简化一下。

  1. 3个用户帐户用户的角色
  2. 用户在 A 部门工作
  3. 但并非所有具有帐户用户角色的用户都可以以相同的值进行授权。可能是授权值为10,000美元的2个用户和1个用户 $4,000。

在上面的例子中,我们使用角色管理器进行了一些变通,它为不同的授权目的创建了许多不同的角色。这使得管理变得更加复杂。因此,在这种情况下,我们做另一种解决方案,仍然使用内置的,但添加另一层的授权,即“公司树”,它作为多级授权,只是为了解决组织问题。

看看我们怎么做。

img

公司Doctype

img

工作流程,文档类型,相关人员

现在让我们看看销售发票的主要文件。我们如何覆盖它并添加自定义验证。首先我们创建如下的文件目录

umserp
|_overrides
|_doctypes
|_sales_invoice.py
from __future__ import unicode_literals
import frappe
import erpnext
from umserp.core.foundations.reporting import render_report
from erpnext.accounts.doctype.sales_invoice.sales_invoice import SalesInvoice
from frappe import _
from frappe.utils import add_days, cint, cstr, flt, formatdate, get_link_to_form, getdate, nowdate, now
from umserp.core.utils.accounting import money_to_words_malay
from umserp.umserp.doctype.module_mapper.module_mapper import validate_financial_period
from umserp.umserp.doctype.company_tree.company_tree import validate_approving_tree_authority
from frappe.model.naming import make_autoname
from erpnext.accounts.utils import get_account_currency, get_fiscal_year
import json
from frappe.model.workflow import get_workflow,get_transitions
from frappe.query_builder import DocType
from frappe.workflow.doctype.workflow_action.workflow_action import get_doc_workflow_state
from frappe.model.workflow import get_workflow_name

# sales_invoice.py
class UMSSalesInvoice(SalesInvoice):
def _validate(self):
super(UMSSalesInvoice,self).validate()
self.convert_amount_into_words()

if self._action == "submit":
self._validate_financial_period() # this is another custom validation for financial period

self._validate_approving_tree_authority() # this is the validation

if self._validate_posting_date():
self.clear_payment_schedule()
self.update_posting_date()
self.set_payment_schedule()
super(UMSSalesInvoice,self).set_missing_values()

方法 self._validate_approving_tree_authority() 取自公司树 doctype。这个逻辑太长了,所以我不能在这个例子中包含它。

我们使这些代码适用于 ERPNext 的最后一部分是在 hook.py 中包含重写类。

override_doctype_class = {
"Sales Invoice": "umserp.overrides.doctype.sales_invoice.UMSSalesInvoice",
}

现在,系统会自动识别我们的代码作为首先要读取的主代码。

我在这里想说的是,ERPNext 是一个由 Frappe 框架授权的系统,可以很容易地定制,即使是一个公司可以拥有的复杂规则。当然,需求必须在一定程度上发生变化,但是如果一个框架具有这样的灵活性,那就太好了。

Happy reading. 阅读愉快。

备注: 感谢我伟大而热情的团队,使一切成为可能,而我永远不能做到没有他们。还要感谢 Frappe Team 制作了这样一个伟大的框架。