summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/services/data_manager.js
blob: f5bb482a66d0e78253ef47d1404ec15c1bd3afad (plain)
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
odoo.define('web.DataManager', function (require) {
"use strict";

var config = require('web.config');
var core = require('web.core');
var rpc = require('web.rpc');
var session = require('web.session');
var utils = require('web.utils');

return core.Class.extend({
    init: function () {
        this._init_cache();
        core.bus.on('clear_cache', this, this.invalidate.bind(this));
    },

    _init_cache: function () {
        this._cache = {
            actions: {},
            filters: {},
            views: {},
        };
    },

    /**
     * Invalidates the whole cache
     * Suggestion: could be refined to invalidate some part of the cache
     */
    invalidate: function () {
        session.invalidateCacheKey('load_menus');
        this._init_cache();
    },

    /**
     * Loads an action from its id or xmlid.
     *
     * @param {int|string} [action_id] the action id or xmlid
     * @param {Object} [additional_context] used to load the action
     * @return {Promise} resolved with the action whose id or xmlid is action_id
     */
    load_action: function (action_id, additional_context) {
        var self = this;
        var key = this._gen_key(action_id, additional_context || {});

        if (config.isDebug('assets') || !this._cache.actions[key]) {
            this._cache.actions[key] = rpc.query({
                route: "/web/action/load",
                params: {
                    action_id: action_id,
                    additional_context: additional_context,
                },
            }).then(function (action) {
                self._cache.actions[key] = action.no_cache ? null : self._cache.actions[key];
                return action;
            }).guardedCatch(() => this._invalidate('actions', key));
        }

        return this._cache.actions[key].then(function (action) {
            return $.extend(true, {}, action);
        });
    },

    /**
     * Loads various information concerning views: fields_view for each view,
     * the fields of the corresponding model, and optionally the filters.
     *
     * @param {Object} params
     * @param {String} params.model
     * @param {Object} params.context
     * @param {Array} params.views_descr array of [view_id, view_type]
     * @param {Object} [options={}] dictionary of various options:
     *     - options.load_filters: whether or not to load the filters,
     *     - options.action_id: the action_id (required to load filters),
     *     - options.toolbar: whether or not a toolbar will be displayed,
     * @return {Promise} resolved with the requested views information
     */
    load_views: async function ({ model, context, views_descr } , options = {}) {
        const viewsKey = this._gen_key(model, views_descr, options, context);
        const filtersKey = this._gen_key(model, options.action_id);
        const withFilters = Boolean(options.load_filters);
        const shouldLoadViews = config.isDebug('assets') || !this._cache.views[viewsKey];
        const shouldLoadFilters = config.isDebug('assets') || (
            withFilters && !this._cache.filters[filtersKey]
        );
        if (shouldLoadViews) {
            // Views info should be loaded
            options.load_filters = shouldLoadFilters;
            this._cache.views[viewsKey] = rpc.query({
                args: [],
                kwargs: { context, options, views: views_descr },
                model,
                method: 'load_views',
            }).then(result => {
                // Freeze the fields dict as it will be shared between views and
                // no one should edit it
                utils.deepFreeze(result.fields);
                for (const [viewId, viewType] of views_descr) {
                    const fvg = result.fields_views[viewType];
                    fvg.viewFields = fvg.fields;
                    fvg.fields = result.fields;
                }

                // Insert filters, if any, into the filters cache
                if (shouldLoadFilters) {
                    this._cache.filters[filtersKey] = Promise.resolve(result.filters);
                }
                return result.fields_views;
            }).guardedCatch(() => this._invalidate('views', viewsKey));
        }
        const result = await this._cache.views[viewsKey];
        if (withFilters && result.search) {
            if (shouldLoadFilters) {
                await this.load_filters({
                    actionId: options.action_id,
                    context,
                    forceReload: false,
                    modelName: model,
                });
            }
            result.search.favoriteFilters = await this._cache.filters[filtersKey];
        }
        return result;
    },

    /**
     * Loads the filters of a given model and optional action id.
     *
     * @param {Object} params
     * @param {number} params.actionId
     * @param {Object} params.context
     * @param {boolean} [params.forceReload=true] can be set to false to prevent forceReload
     * @param {string} params.modelName
     * @return {Promise} resolved with the requested filters
     */
    load_filters: function (params) {
        const key = this._gen_key(params.modelName, params.actionId);
        const forceReload = params.forceReload !== false && config.isDebug('assets');
        if (forceReload || !this._cache.filters[key]) {
            this._cache.filters[key] = rpc.query({
                args: [params.modelName, params.actionId],
                kwargs: {
                    context: params.context || {},
                    // get_context() de dataset
                },
                model: 'ir.filters',
                method: 'get_filters',
            }).guardedCatch(() => this._invalidate('filters', key));
        }
        return this._cache.filters[key];
    },

    /**
     * Calls 'create_or_replace' on 'ir_filters'.
     *
     * @param {Object} [filter] the filter description
     * @return {Promise} resolved with the id of the created or replaced filter
     */
    create_filter: function (filter) {
        return rpc.query({
                args: [filter],
                model: 'ir.filters',
                method: 'create_or_replace',
            })
            .then(filterId => {
                const filtersKey = this._gen_key(filter.model_id, filter.action_id);
                this._invalidate('filters', filtersKey);
                return filterId;
            });
    },

    /**
     * Calls 'unlink' on 'ir_filters'.
     *
     * @param {integer} filterId Id of the filter to remove
     * @return {Promise}
     */
    delete_filter: function (filterId) {
        return rpc.query({
                args: [filterId],
                model: 'ir.filters',
                method: 'unlink',
            })
            // Invalidate the whole cache since we have no idea where the filter came from.
            .then(() => this._invalidate('filters'));
    },

    /**
     * Private function that generates a cache key from its arguments
     */
    _gen_key: function () {
        return _.map(Array.prototype.slice.call(arguments), function (arg) {
            if (!arg) {
                return false;
            }
            return _.isObject(arg) ? JSON.stringify(arg) : arg;
        }).join(',');
    },

    /**
     * Invalidate a cache entry or a whole cache section.
     *
     * @private
     * @param {string} section
     * @param {string} key
     */
    _invalidate(section, key) {
        if (key) {
            delete this._cache[section][key];
        } else {
            this._cache[section] = {};
        }
    },
});

});

odoo.define('web.data_manager', function (require) {
"use strict";

var DataManager = require('web.DataManager');

var data_manager = new DataManager();

return data_manager;

});