odoo16前端框架源码阅读——ormService.js

2023-11-09 06:28

本文主要是介绍odoo16前端框架源码阅读——ormService.js,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

odoo16前端框架源码阅读——ormService.js

路径:addons\web\static\src\core\orm_service.js

简单翻译一下代码中的注释:

ORM服务是js代码和python的ORM层通信的标准方法。

然后讲了One2many and Many2many特使的指令格式,每个指令都是3元组,其中:

第一个参数是固定的整数从0-6,代表指令本身

第二个参数:要么是0(新增记录的时候)要么是关联的记录id(其他update,delete,link,unlink情况)

第三个参数 要么是value(新增或者更新),要么是新的ids(command set) 要么是0,(删除,unlink,link,clear),

再往后,就是几个验证函数,没啥好说的

重点来了:

export class ORM {constructor(rpc, user) {this.rpc = rpc;this.user = user;this._silent = false;}get silent() {return Object.assign(Object.create(this), { _silent: true });}

这里定义并导出了ORM类, 构造函数中引用了rpc和user服务,并且还有一个私有变量_silent, 这个变量暂时不清楚干嘛的,下面这句有意思

 return Object.assign(Object.create(this), { _silent: true });

返回一个同样的orm对象,只是_silent的值编程了true。

这里提一嘴:

Object.create 是创建一个跟自己同样的对象

Object.assign(target,souce1,source2…) 是将第一个对象后面的所有对象的属性付给第一个对象(目标对象),同名的属性会覆盖。

继续往下看call方法,这个是ormService的核心函数

 call(model, method, args = [], kwargs = {}) {validateModel(model);const url = `/web/dataset/call_kw/${model}/${method}`;const fullContext = Object.assign({}, this.user.context, kwargs.context || {});const fullKwargs = Object.assign({}, kwargs, { context: fullContext });const params = {model,method,args,kwargs: fullKwargs,};return this.rpc(url, params, { silent: this._silent });}

首先验证了模型名是否合法, 是否是字符串,并且字符串长度是否等于0,这有点不太严谨啊,怎么也应该验证个.吧,

function validateModel(value) {if (typeof value !== "string" || value.length === 0) {throw new Error(`Invalid model name: ${value}`);}
}

然后就是拼凑params, 就当前用户的context, kwargs.context, kwargs 拼凑成一个对象fullKwargs,然后再跟model,method,args组成一个参数对象params

最后调用rpc, 注意,发送到的url是

 const url = `/web/dataset/call_kw/${model}/${method}`;

我们开看看这个路由都干了啥

文件路径: addons\web\controllers\dataset.py

    @http.route(['/web/dataset/call_kw', '/web/dataset/call_kw/<path:path>'], type='json', auth="user")def call_kw(self, model, method, args, kwargs, path=None):return self._call_kw(model, method, args, kwargs)

我稍微修改了一下代码,将几个参数都打印了出来

    def call_kw(self, model, method, args, kwargs, path=None):print(model)print(method)print(args)print(kwargs)print(path)return self._call_kw(model, method, args, kwargs)

打印结果:

res.users
systray_get_activities
[]
{'context': {'lang': 'zh_CN', 'tz': 'Asia/Shanghai', 'uid': 2, 'allowed_company_ids': [1]}}
res.users/systray_get_activities

这样就一目了然了,

然后调用了内部方法

 return self._call_kw(model, method, args, kwargs)
    def _call_kw(self, model, method, args, kwargs):check_method_name(method)return call_kw(request.env[model], method, args, kwargs)

第一步检查方法名,凡是以下划线开头或者init的方法都不允许远程调用。

def check_method_name(name):""" Raise an ``AccessError`` if ``name`` is a private method name. """if regex_private.match(name):raise AccessError(_('Private methods (such as %s) cannot be called remotely.', name))

然后第二步又调用了call_kw ,这个call_kw 非彼call_kw, 因为前面没有加self,事实上,这个call_kw是从api中引入的

from odoo.api import call_kw

好吧,继续跟踪,到了api中的call_kw

odoo/api.py

def call_kw(model, name, args, kwargs):""" Invoke the given method ``name`` on the recordset ``model``. """method = getattr(type(model), name)api = getattr(method, '_api', None)if api == 'model':result = _call_kw_model(method, model, args, kwargs)elif api == 'model_create':result = _call_kw_model_create(method, model, args, kwargs)else:result = _call_kw_multi(method, model, args, kwargs)model.env.flush_all()return result

跟踪到这里,有点意思了,先获取了model的方法,并获取了方法的_api属性, 这个属性是怎么来的呢, 看到api就想到了那几个装饰器,怀着碰碰运气的想法查看了一下api.py, 下面是我们熟悉的@api.model 的源码,果然是在装饰器里设置了_api这个属性

def model(method):""" Decorate a record-style method where ``self`` is a recordset, but itscontents is not relevant, only the model is. Such a method::@api.modeldef method(self, args):..."""if method.__name__ == 'create':return model_create_single(method)method._api = 'model'return method

到这里就明了了,根据不同的装饰器,调用不同的方法来处理rpc请求。

我们言归正传,继续回到ormService

call后面的函数,其实都是最终都是调用了call方法, 知识在调用之前,传递了不同的参数,这个要结合后台的python代码再去细看,这里就不展开了。

下面是ormService的最后部分:

1、在调用orm方法的时候可以设置一些选项,比如

const result = await this.orm.withOption({shadow: true}).read('res.partner', [id]);

shadow是个什么鬼?

2、终于出现了ormService的定义

它依赖于rpc和user两个服务,rpc发送http请求,而user提供当前用户的上下文环境

列举了支持的方法,这些方法在ORM类中都有定义。

start函数: 参数是env和{rpc,user} 返回了一个ORM实例。

最后注册了这个ormService服务。

/*** Note:** when we will need a way to configure a rpc (for example, to setup a "shadow"* flag, or some way of not displaying errors), we can use the following api:** this.orm = useService('orm');** ...** const result = await this.orm.withOption({shadow: true}).read('res.partner', [id]);*/
export const ormService = {dependencies: ["rpc", "user"],async: ["call","create","nameGet","read","readGroup","search","searchRead","unlink","webSearchRead","write",],start(env, { rpc, user }) {return new ORM(rpc, user);},
};registry.category("services").add("orm", ormService);

附录:ormService.js 代码

/** @odoo-module **/import { registry } from "./registry";/*** This ORM service is the standard way to interact with the ORM in python from* the javascript codebase.*/// -----------------------------------------------------------------------------
// ORM
// -----------------------------------------------------------------------------/*** One2many and Many2many fields expect a special command to manipulate the* relation they implement.** Internally, each command is a 3-elements tuple where the first element is a* mandatory integer that identifies the command, the second element is either* the related record id to apply the command on (commands update, delete,* unlink and link) either 0 (commands create, clear and set), the third* element is either the ``values`` to write on the record (commands create* and update) either the new ``ids`` list of related records (command set),* either 0 (commands delete, unlink, link, and clear).*/
export const x2ManyCommands = {// (0, virtualID | false, { values })CREATE: 0,create(virtualID, values) {delete values.id;return [x2ManyCommands.CREATE, virtualID || false, values];},// (1, id, { values })UPDATE: 1,update(id, values) {delete values.id;return [x2ManyCommands.UPDATE, id, values];},// (2, id[, _])DELETE: 2,delete(id) {return [x2ManyCommands.DELETE, id, false];},// (3, id[, _]) removes relation, but not linked record itselfFORGET: 3,forget(id) {return [x2ManyCommands.FORGET, id, false];},// (4, id[, _])LINK_TO: 4,linkTo(id) {return [x2ManyCommands.LINK_TO, id, false];},// (5[, _[, _]])DELETE_ALL: 5,deleteAll() {return [x2ManyCommands.DELETE_ALL, false, false];},// (6, _, ids) replaces all linked records with provided idsREPLACE_WITH: 6,replaceWith(ids) {return [x2ManyCommands.REPLACE_WITH, false, ids];},
};function validateModel(value) {if (typeof value !== "string" || value.length === 0) {throw new Error(`Invalid model name: ${value}`);}
}
function validatePrimitiveList(name, type, value) {if (!Array.isArray(value) || value.some((val) => typeof val !== type)) {throw new Error(`Invalid ${name} list: ${value}`);}
}
function validateObject(name, obj) {if (typeof obj !== "object" || obj === null || Array.isArray(obj)) {throw new Error(`${name} should be an object`);}
}
function validateArray(name, array) {if (!Array.isArray(array)) {throw new Error(`${name} should be an array`);}
}export class ORM {constructor(rpc, user) {this.rpc = rpc;this.user = user;this._silent = false;}get silent() {return Object.assign(Object.create(this), { _silent: true });}call(model, method, args = [], kwargs = {}) {validateModel(model);const url = `/web/dataset/call_kw/${model}/${method}`;const fullContext = Object.assign({}, this.user.context, kwargs.context || {});const fullKwargs = Object.assign({}, kwargs, { context: fullContext });const params = {model,method,args,kwargs: fullKwargs,};return this.rpc(url, params, { silent: this._silent });}create(model, records, kwargs = {}) {validateArray("records", records);for (const record of records) {validateObject("record", record);}return this.call(model, "create", records, kwargs);}nameGet(model, ids, kwargs = {}) {validatePrimitiveList("ids", "number", ids);if (!ids.length) {return Promise.resolve([]);}return this.call(model, "name_get", [ids], kwargs);}read(model, ids, fields, kwargs = {}) {validatePrimitiveList("ids", "number", ids);if (fields) {validatePrimitiveList("fields", "string", fields);}if (!ids.length) {return Promise.resolve([]);}return this.call(model, "read", [ids, fields], kwargs);}readGroup(model, domain, fields, groupby, kwargs = {}) {validateArray("domain", domain);validatePrimitiveList("fields", "string", fields);validatePrimitiveList("groupby", "string", groupby);return this.call(model, "read_group", [], { ...kwargs, domain, fields, groupby });}search(model, domain, kwargs = {}) {validateArray("domain", domain);return this.call(model, "search", [domain], kwargs);}searchRead(model, domain, fields, kwargs = {}) {validateArray("domain", domain);if (fields) {validatePrimitiveList("fields", "string", fields);}return this.call(model, "search_read", [], { ...kwargs, domain, fields });}searchCount(model, domain, kwargs = {}) {validateArray("domain", domain);return this.call(model, "search_count", [domain], kwargs);}unlink(model, ids, kwargs = {}) {validatePrimitiveList("ids", "number", ids);if (!ids.length) {return true;}return this.call(model, "unlink", [ids], kwargs);}webReadGroup(model, domain, fields, groupby, kwargs = {}) {validateArray("domain", domain);validatePrimitiveList("fields", "string", fields);validatePrimitiveList("groupby", "string", groupby);return this.call(model, "web_read_group", [], {...kwargs,groupby,domain,fields,});}webSearchRead(model, domain, fields, kwargs = {}) {validateArray("domain", domain);validatePrimitiveList("fields", "string", fields);return this.call(model, "web_search_read", [], { ...kwargs, domain, fields });}write(model, ids, data, kwargs = {}) {validatePrimitiveList("ids", "number", ids);validateObject("data", data);return this.call(model, "write", [ids, data], kwargs);}
}/*** Note:** when we will need a way to configure a rpc (for example, to setup a "shadow"* flag, or some way of not displaying errors), we can use the following api:** this.orm = useService('orm');** ...** const result = await this.orm.withOption({shadow: true}).read('res.partner', [id]);*/
export const ormService = {dependencies: ["rpc", "user"],async: ["call","create","nameGet","read","readGroup","search","searchRead","unlink","webSearchRead","write",],start(env, { rpc, user }) {return new ORM(rpc, user);},
};registry.category("services").add("orm", ormService);

这篇关于odoo16前端框架源码阅读——ormService.js的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/374441

相关文章

Go中sync.Once源码的深度讲解

《Go中sync.Once源码的深度讲解》sync.Once是Go语言标准库中的一个同步原语,用于确保某个操作只执行一次,本文将从源码出发为大家详细介绍一下sync.Once的具体使用,x希望对大家有... 目录概念简单示例源码解读总结概念sync.Once是Go语言标准库中的一个同步原语,用于确保某个操

Vue项目中Element UI组件未注册的问题原因及解决方法

《Vue项目中ElementUI组件未注册的问题原因及解决方法》在Vue项目中使用ElementUI组件库时,开发者可能会遇到一些常见问题,例如组件未正确注册导致的警告或错误,本文将详细探讨这些问题... 目录引言一、问题背景1.1 错误信息分析1.2 问题原因二、解决方法2.1 全局引入 Element

详解如何在React中执行条件渲染

《详解如何在React中执行条件渲染》在现代Web开发中,React作为一种流行的JavaScript库,为开发者提供了一种高效构建用户界面的方式,条件渲染是React中的一个关键概念,本文将深入探讨... 目录引言什么是条件渲染?基础示例使用逻辑与运算符(&&)使用条件语句列表中的条件渲染总结引言在现代

详解Vue如何使用xlsx库导出Excel文件

《详解Vue如何使用xlsx库导出Excel文件》第三方库xlsx提供了强大的功能来处理Excel文件,它可以简化导出Excel文件这个过程,本文将为大家详细介绍一下它的具体使用,需要的小伙伴可以了解... 目录1. 安装依赖2. 创建vue组件3. 解释代码在Vue.js项目中导出Excel文件,使用第三

Java实现Excel与HTML互转

《Java实现Excel与HTML互转》Excel是一种电子表格格式,而HTM则是一种用于创建网页的标记语言,虽然两者在用途上存在差异,但有时我们需要将数据从一种格式转换为另一种格式,下面我们就来看看... Excel是一种电子表格格式,广泛用于数据处理和分析,而HTM则是一种用于创建网页的标记语言。虽然两

vue解决子组件样式覆盖问题scoped deep

《vue解决子组件样式覆盖问题scopeddeep》文章主要介绍了在Vue项目中处理全局样式和局部样式的方法,包括使用scoped属性和深度选择器(/deep/)来覆盖子组件的样式,作者建议所有组件... 目录前言scoped分析deep分析使用总结所有组件必须加scoped父组件覆盖子组件使用deep前言

VUE动态绑定class类的三种常用方式及适用场景详解

《VUE动态绑定class类的三种常用方式及适用场景详解》文章介绍了在实际开发中动态绑定class的三种常见情况及其解决方案,包括根据不同的返回值渲染不同的class样式、给模块添加基础样式以及根据设... 目录前言1.动态选择class样式(对象添加:情景一)2.动态添加一个class样式(字符串添加:情

Java汇编源码如何查看环境搭建

《Java汇编源码如何查看环境搭建》:本文主要介绍如何在IntelliJIDEA开发环境中搭建字节码和汇编环境,以便更好地进行代码调优和JVM学习,首先,介绍了如何配置IntelliJIDEA以方... 目录一、简介二、在IDEA开发环境中搭建汇编环境2.1 在IDEA中搭建字节码查看环境2.1.1 搭建步

Node.js 中 http 模块的深度剖析与实战应用小结

《Node.js中http模块的深度剖析与实战应用小结》本文详细介绍了Node.js中的http模块,从创建HTTP服务器、处理请求与响应,到获取请求参数,每个环节都通过代码示例进行解析,旨在帮... 目录Node.js 中 http 模块的深度剖析与实战应用一、引言二、创建 HTTP 服务器:基石搭建(一

React实现原生APP切换效果

《React实现原生APP切换效果》最近需要使用Hybrid的方式开发一个APP,交互和原生APP相似并且需要IM通信,本文给大家介绍了使用React实现原生APP切换效果,文中通过代码示例讲解的非常... 目录背景需求概览技术栈实现步骤根据 react-router-dom 文档配置好路由添加过渡动画使用