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),

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

重点来了:

js 复制代码
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, 这个变量暂时不清楚干嘛的,下面这句有意思

js 复制代码
 return Object.assign(Object.create(this), { _silent: true });

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

这里提一嘴:

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

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

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

js 复制代码
 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,这有点不太严谨啊,怎么也应该验证个.吧,

js 复制代码
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

python 复制代码
    @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)

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

python 复制代码
    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)

打印结果:

shell 复制代码
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)
python 复制代码
    def _call_kw(self, model, method, args, kwargs):
        check_method_name(method)
        return call_kw(request.env[model], method, args, kwargs)

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

python 复制代码
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

python 复制代码
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这个属性

python 复制代码
def model(method):
    """ Decorate a record-style method where ``self`` is a recordset, but its
        contents is not relevant, only the model is. Such a method::

            @api.model
            def 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方法的时候可以设置一些选项,比如

js 复制代码
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服务。

js 复制代码
/**
 * 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 代码

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 itself
    FORGET: 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 ids
    REPLACE_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);
相关推荐
Myli_ing12 分钟前
考研倒计时-配色+1
前端·javascript·考研
余道各努力,千里自同风14 分钟前
前端 vue 如何区分开发环境
前端·javascript·vue.js
PandaCave21 分钟前
vue工程运行、构建、引用环境参数学习记录
javascript·vue.js·学习
软件小伟23 分钟前
Vue3+element-plus 实现中英文切换(Vue-i18n组件的使用)
前端·javascript·vue.js
醉の虾1 小时前
Vue3 使用v-for 渲染列表数据后更新
前端·javascript·vue.js
张小小大智慧1 小时前
TypeScript 的发展与基本语法
前端·javascript·typescript
hummhumm1 小时前
第 22 章 - Go语言 测试与基准测试
java·大数据·开发语言·前端·python·golang·log4j
asleep7011 小时前
第8章利用CSS制作导航菜单
前端·css
hummhumm1 小时前
第 28 章 - Go语言 Web 开发入门
java·开发语言·前端·python·sql·golang·前端框架
幼儿园的小霸王2 小时前
通过socket设置版本更新提示
前端·vue.js·webpack·typescript·前端框架·anti-design-vue