跳到主要内容

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

· 阅读需 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 制作了这样一个伟大的框架。