• 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 });
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

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

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

    返回一个同样的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 });
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

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

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

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

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

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

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

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

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

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

        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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    打印结果:

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

    这样就一目了然了,

    然后调用了内部方法

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

    第一步检查方法名,凡是以下划线开头或者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))
    
    • 1
    • 2
    • 3
    • 4

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

    from odoo.api import call_kw
    
    • 1

    好吧,继续跟踪,到了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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

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

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

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

    下面是ormService的最后部分:

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

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

    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    附录: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 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);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
  • 相关阅读:
    String长度限制?
    模拟信号隔离放大器PCB焊接模块电压电流信号变送0-5v0-10v4-20mA0-20mA1-5v0-12v0-24v
    微信定时发圈,让你轻松管理朋友圈!
    电气工程的标准是什么
    19.03 容器的说明和简单应用例续
    黑客技术(网络安全)学习
    MySQL数据库管理
    金仓数据库KingbaseES接口协议解析工具使用指南(3. 解析工具的使用)
    怎么设置 idea terminal 窗口的编码格式
    ECC有关DER文件的解析(Java)
  • 原文地址:https://blog.csdn.net/jiafeitutu/article/details/134298101