summaryrefslogtreecommitdiff
path: root/addons/web/static
diff options
context:
space:
mode:
authorqsm-odoo <qsm@odoo.com>2017-02-28 11:48:00 +0100
committerAaron Bohy <aab@odoo.com>2017-04-11 19:44:38 +0200
commit5804ed100ec790a36c5ff30a217914950cd78098 (patch)
treea9f7ecf403cc8682c52ecce354ba082e9dee1a7c /addons/web/static
parente5740270568404d71a3bc093074a085c9c2eded8 (diff)
[IMP] web, *: compute contexts and domains from datapoint
This commit tries to refactore the management of contexts and domains in the webclient. - Get rid of CompoundDomain: use standard array manipulation to join multiple domains or use the Domain class (to be improved) - Get rid of data.js build_domain and build_context methods - Link this.record to the data point from the basic model for all fields (not only relational fields) - Add a getContext and a getDomain function to the basic model data point (localData element) so that AbstractField implementations can use them thanks to their this.record element. - Remove the autocontext binding for field RPCs (see FieldManagerMixin)
Diffstat (limited to 'addons/web/static')
-rw-r--r--addons/web/static/src/js/_deprecated/data.js72
-rw-r--r--addons/web/static/src/js/chrome/search_inputs.js39
-rw-r--r--addons/web/static/src/js/core/context.js8
-rw-r--r--addons/web/static/src/js/core/domain.js42
-rw-r--r--addons/web/static/src/js/core/pyeval.js8
-rw-r--r--addons/web/static/src/js/fields/abstract_field.js15
-rw-r--r--addons/web/static/src/js/fields/basic_fields.js4
-rw-r--r--addons/web/static/src/js/fields/relational_fields.js100
-rw-r--r--addons/web/static/src/js/views/basic/basic_model.js99
-rw-r--r--addons/web/static/src/js/views/field_manager_mixin.js35
-rw-r--r--addons/web/static/src/js/views/form/form_controller.js6
-rw-r--r--addons/web/static/test/core/pyeval_tests.js21
-rw-r--r--addons/web/static/test/fields/relational_fields_tests.js24
-rw-r--r--addons/web/static/test/views/basic_model_tests.js60
14 files changed, 264 insertions, 269 deletions
diff --git a/addons/web/static/src/js/_deprecated/data.js b/addons/web/static/src/js/_deprecated/data.js
index 3c95ababc3a..14b3daeb2c6 100644
--- a/addons/web/static/src/js/_deprecated/data.js
+++ b/addons/web/static/src/js/_deprecated/data.js
@@ -64,8 +64,7 @@ var Query = Class.extend({
if (!to_set.hasOwnProperty(key)) { continue; }
switch(key) {
case 'filter':
- q._filter = new CompoundDomain(
- q._filter, to_set.filter);
+ q._filter = (q._filter || []).concat(to_set.filter || []);
break;
case 'context':
q._context = new Context(
@@ -729,77 +728,12 @@ var DataSetSearch = DataSet.extend({
}
});
-var CompoundDomain = Class.extend({
- init: function () {
- this.__ref = "compound_domain";
- this.__domains = [];
- this.__eval_context = null;
- var self = this;
- _.each(arguments, function (x) {
- self.add(x);
- });
- },
- add: function (domain) {
- this.__domains.push(domain);
- return this;
- },
- set_eval_context: function (eval_context) {
- this.__eval_context = eval_context;
- return this;
- },
- get_eval_context: function () {
- return this.__eval_context;
- },
- eval: function () {
- return pyeval.eval('domain', this);
- },
-});
-
-function rawify (data) {
- // replaces the value of relational fields by their raw value (the id, if
- // any, for many2ones and an array of ids for x2manys), in turn
- return _.mapObject(data, function (value) {
- if (value instanceof moment) {
- return moment;
- }
- if (_.isObject(value)) {
- return value.type === 'record' ? value.res_id : value.res_ids;
- }
- return value;
- });
-}
-
-function build_eval_context (record) {
- var field_values = rawify(_.extend({}, record.data, { active_model: record.model }));
- if ('id' in record.data) {
- field_values.active_id = record.data.id;
- field_values.active_ids = [record.data.id];
- }
- // FIXME: parent?
- return new Context(field_values);
-}
-
-function build_context(record, context) {
- context = context || {};
- var eval_context = build_eval_context(record);
- return new Context(context).set_eval_context(eval_context);
-}
-
-function build_domain(record, domain) {
- domain = domain || [];
- var eval_context = build_eval_context(record);
- return new CompoundDomain(domain).set_eval_context(eval_context);
-}
-
var data = {
Query: Query,
DataSet: DataSet,
DataSetStatic: DataSetStatic,
DataSetSearch: DataSetSearch,
- CompoundDomain: CompoundDomain,
noDisplayContent: "<em class=\"text-warning\">" + _t("Unnamed") + "</em>",
- build_context: build_context,
- build_domain: build_domain,
};
@@ -841,11 +775,11 @@ var Model = Class.extend({
* Fetches the model's domain, combined with the provided domain if any
*
* @param {Array} [domain] to combine with the model's internal domain
- * @returns {instance.web.CompoundDomain} The model's internal domain, or the AND-ed union of the model's internal domain and the provided domain
+ * @returns {Array} The model's internal domain, or the AND-ed union of the model's internal domain and the provided domain
*/
domain: function (domain) {
if (!domain) { return this._domain; }
- return new data.CompoundDomain(this._domain, domain);
+ return this._domain.concat(domain);
},
/**
* Fetches the combination of the user's context and the domain context,
diff --git a/addons/web/static/src/js/chrome/search_inputs.js b/addons/web/static/src/js/chrome/search_inputs.js
index df5abe22a93..6855520a39b 100644
--- a/addons/web/static/src/js/chrome/search_inputs.js
+++ b/addons/web/static/src/js/chrome/search_inputs.js
@@ -3,7 +3,7 @@ odoo.define('web.search_inputs', function (require) {
var Context = require('web.Context');
var core = require('web.core');
-var data = require('web.data');
+var Domain = require('web.Domain');
var field_utils = require('web.field_utils');
var pyeval = require('web.pyeval');
var time = require('web.time');
@@ -151,27 +151,27 @@ var Field = Input.extend( /** @lends instance.web.search.Field# */ {
var domain = this.attrs.filter_domain;
if (domain) {
value_to_domain = function (facetValue) {
- return new data.CompoundDomain(domain)
- .set_eval_context({self: self.value_from(facetValue)});
+ return Domain.prototype.stringToArray(
+ domain,
+ {self: self.value_from(facetValue)}
+ );
};
} else {
value_to_domain = function (facetValue) {
return self.make_domain(
self.attrs.name,
self.attrs.operator || self.default_operator,
- facetValue);
+ facetValue
+ );
};
}
var domains = facet.values.map(value_to_domain);
-
- if (domains.length === 1) { return domains[0]; }
- for (var i = domains.length; --i;) {
- domains.unshift(['|']);
+ if (domains.length === 1) {
+ return domains[0];
}
- return _.extend(new data.CompoundDomain(), {
- __domains: domains
- });
+ var ors = _.times(domains.length - 1, _.constant("|"));
+ return ors.concat.apply(ors, domains);
}
});
@@ -421,7 +421,7 @@ var ManyToOneField = CharField.extend({
var args = this.attrs.domain;
if (typeof args === 'string') {
try {
- args = pyeval.eval('domain', args);
+ args = Domain.prototype.stringToArray(args);
} catch(e) {
args = [];
}
@@ -625,14 +625,15 @@ var FilterGroup = Input.extend(/** @lends instance.web.search.FilterGroup# */{
.reject(_.isEmpty)
.value();
- if (!domains.length) { return; }
- if (domains.length === 1) { return domains[0]; }
- for (var i=domains.length; --i;) {
- domains.unshift(['|']);
+ if (!domains.length) {
+ return;
}
- return _.extend(new data.CompoundDomain(), {
- __domains: domains
- });
+ if (domains.length === 1) {
+ return domains[0];
+ }
+
+ var ors = _.times(domains.length - 1, _.constant("|"));
+ return ors.concat.apply(ors, domains);
},
toggle_filter: function (e) {
e.preventDefault();
diff --git a/addons/web/static/src/js/core/context.js b/addons/web/static/src/js/core/context.js
index 961c67b898a..fe271afd244 100644
--- a/addons/web/static/src/js/core/context.js
+++ b/addons/web/static/src/js/core/context.js
@@ -14,6 +14,11 @@ var Context = Class.extend({
self.add(x);
});
},
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
add: function (context) {
this.__contexts.push(context);
return this;
@@ -22,9 +27,6 @@ var Context = Class.extend({
this.__eval_context = eval_context;
return this;
},
- get_eval_context: function () {
- return this.__eval_context;
- },
eval: function () {
return pyeval.eval('context', this);
},
diff --git a/addons/web/static/src/js/core/domain.js b/addons/web/static/src/js/core/domain.js
index 11d6a59ec33..2503042d070 100644
--- a/addons/web/static/src/js/core/domain.js
+++ b/addons/web/static/src/js/core/domain.js
@@ -12,20 +12,22 @@ var Domain = collections.Tree.extend({
/**
* @constructor
* @param {string|Array|boolean|undefined} domain
- * - The given domain can be:
+ * The given domain can be:
* * a string representation of the Python prefix-array
* representation of the domain.
* * a JS prefix-array representation of the domain.
* * a boolean where the "true" domain match all records and the
* "false" domain does not match any records.
* * undefined, considered as the false boolean.
+ * @param {Object} [evalContext] - in case the given domain is a string, an
+ * evaluation context might be needed
*/
- init: function (domain) {
+ init: function (domain, evalContext) {
this._super.apply(this, arguments);
if (domain === true || domain === false || domain === undefined) {
this._data = domain || false;
} else {
- this._parse(this.normalizeArray(_.clone(this.stringToArray(domain))));
+ this._parse(this.normalizeArray(_.clone(this.stringToArray(domain, evalContext))));
}
},
@@ -107,6 +109,30 @@ var Domain = collections.Tree.extend({
}
},
+ /**
+ * @returns {Array} JS prefix-array representation of this domain
+ */
+ toArray: function () {
+ if (this._data === false) {
+ throw new Error("'false' domain cannot be converted to array");
+ } else if (this._data === true) {
+ return [];
+ } else {
+ var arr = [this._data];
+ return arr.concat.apply(arr, _.map(this._children, function (child) {
+ return child.toArray();
+ }));
+ }
+ },
+
+ /**
+ * @returns {string} representation of the Python prefix-array
+ * representation of the domain
+ */
+ toString: function () {
+ return Domain.prototype.arrayToString(this.toArray());
+ },
+
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
@@ -179,8 +205,9 @@ var Domain = collections.Tree.extend({
arrayToString: function (domain) {
if (_.isString(domain)) return domain;
return JSON.stringify(domain || [])
- .replace(/false/g, "False")
- .replace(/true/g, "True");
+ .replace(/null/g, "None")
+ .replace(/false/g, "False")
+ .replace(/true/g, "True");
},
/**
* Converts a string representation of the Python prefix-array
@@ -189,11 +216,12 @@ var Domain = collections.Tree.extend({
*
* @static
* @param {string|Array} domain
+ * @param {Object} [evalContext]
* @returns {Array}
*/
- stringToArray: function (domain) {
+ stringToArray: function (domain, evalContext) {
if (!_.isString(domain)) return domain;
- return pyeval.eval("domain", domain || "[]");
+ return pyeval.eval("domain", domain || "[]", evalContext);
},
/**
* Makes implicit "&" operators explicit in the given JS prefix-array
diff --git a/addons/web/static/src/js/core/pyeval.js b/addons/web/static/src/js/core/pyeval.js
index 0227668e1c8..75efc9e813f 100644
--- a/addons/web/static/src/js/core/pyeval.js
+++ b/addons/web/static/src/js/core/pyeval.js
@@ -867,12 +867,6 @@ function eval_domains(domains, evaluation_context) {
evaluation_context.context = evaluation_context;
domain_array_to_combine = py.eval(domain.__debug, wrap_context(evaluation_context));
break;
- case 'compound_domain':
- var eval_context = eval_contexts([domain.__eval_context]);
- domain_array_to_combine = eval_domains(
- domain.__domains, _.extend({}, evaluation_context, eval_context)
- );
- break;
default:
domain_array_to_combine = domain;
}
@@ -983,7 +977,7 @@ function pyeval(type, object, context) {
function eval_arg(arg) {
if (typeof arg !== 'object' || !arg.__ref) { return arg; }
switch(arg.__ref) {
- case 'domain': case 'compound_domain':
+ case 'domain':
return pyeval('domains', [arg]);
case 'context': case 'compound_context':
return pyeval('contexts', [arg]);
diff --git a/addons/web/static/src/js/fields/abstract_field.js b/addons/web/static/src/js/fields/abstract_field.js
index 6b914106371..ef67ba9b3c6 100644
--- a/addons/web/static/src/js/fields/abstract_field.js
+++ b/addons/web/static/src/js/fields/abstract_field.js
@@ -81,6 +81,9 @@ var AbstractField = Widget.extend({
// 'name' is the field name displayed by this widget
this.name = name;
+ // the datapoint fetched from the model
+ this.record = record;
+
// the 'field' property is a description of all the various field properties,
// such as the type, the comodel (relation), ...
this.field = record.fields[name];
@@ -208,17 +211,6 @@ var AbstractField = Widget.extend({
this._reset(record, event);
return this._render();
},
- /**
- * @override performModelRPC from ServicesMixin
- * Adds the dataPointID to the option parameter so that the BasicController
- * (the FieldManagerMixin) knows the context to bind to the rpc call.
- */
- performModelRPC: function (model, method, args, kwargs, options) {
- return this._super(model, method, args, kwargs, _.extend({
- dataPointID: this.dataPointID,
- fieldName: this.name,
- }, options || {}));
- },
//--------------------------------------------------------------------------
// Private
@@ -289,6 +281,7 @@ var AbstractField = Widget.extend({
* @param {OdooEvent} event the event that triggered the change
*/
_reset: function (record, event) {
+ this.record = record;
this.value = record.data[this.name];
this.recordData = record.data;
},
diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js
index 6b2cd4d6754..17e4e33f20e 100644
--- a/addons/web/static/src/js/fields/basic_fields.js
+++ b/addons/web/static/src/js/fields/basic_fields.js
@@ -1298,9 +1298,9 @@ var FieldDomain = AbstractField.extend({
// ... then query the model to get the number of matched record if valid
var self = this;
return def.then(function () {
- var context = {}; // FIXME ?
return self.rpc(self.domainModel, "search_count")
- .args([domain,context])
+ .args([domain])
+ .kwargs({context: self.record.getContext({fieldName: self.name})})
.exec()
.then(function (data) {
self._isValidForModel = true;
diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js
index c5750c86735..51760a3c3b8 100644
--- a/addons/web/static/src/js/fields/relational_fields.js
+++ b/addons/web/static/src/js/fields/relational_fields.js
@@ -16,7 +16,6 @@ odoo.define('web.relational_fields', function (require) {
var AbstractField = require('web.AbstractField');
var concurrency = require('web.concurrency');
var config = require('web.config');
-var Context = require('web.Context');
var ControlPanel = require('web.ControlPanel');
var dialogs = require('web.view_dialogs');
var core = require('web.core');
@@ -26,39 +25,10 @@ var field_utils = require('web.field_utils');
var KanbanRenderer = require('web.KanbanRenderer');
var ListRenderer = require('web.ListRenderer');
var Pager = require('web.Pager');
-var pyeval = require('web.pyeval');
var _t = core._t;
var qweb = core.qweb;
-/**
- * Abstract widget to handle relational fields
- */
-var AbstractRelationalField = AbstractField.extend({
- init: function (parent, name, record) {
- this._super.apply(this, arguments);
- // 'domain' and 'context' that should be used by the field widget. It
- // can come from the attrs, and is mostly useful for rpcs
- this.domain = this.attrs.domain || this.field.domain || [];
- this.context = this.attrs.context || this.field.context || {};
-
- // todo: remove this
- this.record = record;
- },
-
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
- /**
- * @private
- */
- _reset: function (record) {
- this.record = record;
- this._super.apply(this, arguments);
- },
-});
-
//------------------------------------------------------------------------------
// Many2one widgets
//------------------------------------------------------------------------------
@@ -104,7 +74,7 @@ var M2ODialog = Dialog.extend({
},
});
-var FieldMany2One = AbstractRelationalField.extend({
+var FieldMany2One = AbstractField.extend({
template: 'FieldMany2One',
custom_events: {
'quick_create': '_onQuickCreate',
@@ -229,6 +199,7 @@ var FieldMany2One = AbstractRelationalField.extend({
if (this.nodeOptions.quick_create) {
this.rpc(this.field.relation, "name_create")
.args([name])
+ .kwargs({context: this.record.getContext({fieldName: this.name})})
.exec()
.then(function (result) {
if (self.mode === "edit") {
@@ -288,18 +259,20 @@ var FieldMany2One = AbstractRelationalField.extend({
var def = $.Deferred();
this.orderer.add(def);
- var exclusion_domain = [];
+ var context = this.record.getContext({fieldName: this.name});
+ var domain = this.record.getDomain({fieldName: this.name});
+
var blacklisted_ids = this._getSearchBlacklist();
if (blacklisted_ids.length > 0) {
- exclusion_domain.push(['id', 'not in', blacklisted_ids]);
+ domain.push(['id', 'not in', blacklisted_ids]);
}
- var domain = new data.CompoundDomain(data.build_domain(this.record, this.domain), exclusion_domain);
this.performModelRPC(this.field.relation, "name_search", [], {
name: search_val,
- args: pyeval.eval("domain", domain),
+ args: domain,
operator: "ilike",
limit: this.limit + 1,
+ context: context,
}).then(function (result) {
// possible selections for the m2o
var values = _.map(result, function (x) {
@@ -320,9 +293,10 @@ var FieldMany2One = AbstractRelationalField.extend({
action: function () {
self.performModelRPC(self.field.relation, "name_search", {
name: search_val,
- args: pyeval.eval('domain', domain),
+ args: domain,
operator: "ilike",
limit: 160,
+ context: context,
}).then(self._searchCreatePopup.bind(self, "search"));
},
classname: 'o_m2o_dropdown_option',
@@ -370,8 +344,8 @@ var FieldMany2One = AbstractRelationalField.extend({
var self = this;
new dialogs.SelectCreateDialog(this, _.extend({}, this.nodeOptions, {
res_model: this.field.relation,
- domain: data.build_domain(this.record, this.domain),
- context: new Context(data.build_context(this.record, this.context), context || {}),
+ domain: this.record.getDomain({fieldName: this.name}),
+ context: _.extend({}, this.record.getContext({fieldName: this.name}), context || {}),
title: (view === 'search' ? _t("Search: ") : _t("Create: ")) + this.string,
initial_ids: ids ? _.map(ids, function (x) { return x[0]; }) : undefined,
initial_view: view,
@@ -405,12 +379,13 @@ var FieldMany2One = AbstractRelationalField.extend({
if (this.mode === 'readonly' && !this.nodeOptions.no_open) {
event.preventDefault();
event.stopPropagation();
- var context = data.build_context(this.record, this.context);
- var args = [[this.value.res_id], context.eval()];
- this.performModelRPC(this.field.relation, 'get_formview_action', args)
- .then(function (action) {
- self.trigger_up('do_action', {action: action});
- });
+ this.performModelRPC(this.field.relation, 'get_formview_action', [
+ [this.value.res_id]
+ ], {
+ context: this.record.getContext({fieldName: this.name}),
+ }).then(function (action) {
+ self.trigger_up('do_action', {action: action});
+ });
}
},
/**
@@ -422,11 +397,14 @@ var FieldMany2One = AbstractRelationalField.extend({
return;
}
var self = this;
- var context = data.build_context(this.record, this.context);
var model = this.field.relation;
var method = 'get_formview_id';
- var args = [[this.value.res_id], context.eval()];
- this.performModelRPC(model, method, args).then(function (view_id) {
+ var context = this.record.getContext({fieldName: this.name});
+ this.performModelRPC(model, method, [
+ [this.value.res_id]
+ ], {
+ context: context,
+ }).then(function (view_id) {
new dialogs.FormViewDialog(self, {
res_model: self.field.relation,
res_id: self.value.res_id,
@@ -492,7 +470,7 @@ var ListFieldMany2One = FieldMany2One.extend({
},
});
-var KanbanFieldMany2One = AbstractRelationalField.extend({
+var KanbanFieldMany2One = AbstractField.extend({
tagName: 'span',
init: function () {
this._super.apply(this, arguments);
@@ -515,9 +493,9 @@ var KanbanFieldMany2One = AbstractRelationalField.extend({
// X2Many widgets
//------------------------------------------------------------------------------
-var FieldX2Many = AbstractRelationalField.extend({
+var FieldX2Many = AbstractField.extend({
tagName: 'div',
- custom_events: _.extend({}, AbstractRelationalField.prototype.custom_events, {
+ custom_events: _.extend({}, AbstractField.prototype.custom_events, {
field_changed: '_onFieldChanged',
kanban_record_delete: '_onDeleteRecord',
list_record_delete: '_onDeleteRecord',
@@ -716,8 +694,8 @@ var FieldX2Many = AbstractRelationalField.extend({
*/
_onOpenRecord: function (event) {
_.extend(event.data, {
- context: data.build_context(this.record, this.context),
- domain: data.build_context(this.record, this.domain),
+ context: this.record.getContext({fieldName: this.name}),
+ domain: this.record.getDomain({fieldName: this.name}),
form_view: this.field.views && this.field.views.form,
readonly: this.mode === 'readonly',
string: this.string,
@@ -810,8 +788,8 @@ var FieldOne2Many = FieldX2Many.extend({
});
} else {
this.trigger_up('add_one2many_record', {
- domain: data.build_domain(this.record, this.domain),
- context: data.build_context(this.record, this.context),
+ domain: this.record.getDomain({fieldName: this.name}),
+ context: this.record.getContext({fieldName: this.name}),
field: this.field,
on_save: function (record) {
this._setValue({
@@ -872,11 +850,13 @@ var FieldMany2Many = FieldX2Many.extend({
*/
_onAddRecord: function () {
var self = this;
- var domain = data.build_domain(this.record, this.domain);
+
+ var domain = this.record.getDomain({fieldName: this.name});
+
new dialogs.SelectCreateDialog(this, {
res_model: this.field.relation,
- domain: new data.CompoundDomain(domain, ["!", ["id", "in", this.value.res_ids]]),
- context: data.build_context(this.record, this.context),
+ domain: domain.concat(["!", ["id", "in", this.value.res_ids]]),
+ context: this.record.getContext({fieldName: this.name}),
title: _t("Add: ") + this.string,
no_create: this.nodeOptions.no_create || !this.activeActions.create,
on_selected: function (res_ids) {
@@ -908,7 +888,7 @@ var FieldMany2Many = FieldX2Many.extend({
},
});
-var FieldMany2ManyTags = AbstractRelationalField.extend({
+var FieldMany2ManyTags = AbstractField.extend({
tag_template: "FieldMany2ManyTag",
className: "o_form_field o_form_field_many2manytags",
replace_element: true,
@@ -1118,9 +1098,9 @@ var FormFieldMany2ManyTags = FieldMany2ManyTags.extend({
},
});
-var FieldMany2ManyCheckBoxes = AbstractRelationalField.extend({
+var FieldMany2ManyCheckBoxes = AbstractField.extend({
template: 'FieldMany2ManyCheckBoxes',
- events: _.extend({}, AbstractRelationalField.prototype.events, {
+ events: _.extend({}, AbstractField.prototype.events, {
change: '_onChange',
}),
supportedFieldTypes: ['many2many'],
diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js
index 5c0facecef1..2c7ec01f9c1 100644
--- a/addons/web/static/src/js/views/basic/basic_model.js
+++ b/addons/web/static/src/js/views/basic/basic_model.js
@@ -79,7 +79,7 @@ odoo.define('web.BasicModel', function (require) {
var AbstractModel = require('web.AbstractModel');
var concurrency = require('web.concurrency');
var Context = require('web.Context');
-var data = require('web.data'); // TODO: remove dependency to data.js
+var Domain = require('web.Domain');
var fieldUtils = require('web.field_utils');
var pyeval = require('web.pyeval');
var session = require('web.session');
@@ -365,6 +365,10 @@ var BasicModel = AbstractModel.extend({
delete element.parentID;
delete element.relation_field;
delete element.rawContext;
+
+ element.getContext = this._getContext.bind(this, record);
+ element.getDomain = this._getDomain.bind(this, record);
+
return element;
},
/**
@@ -929,20 +933,6 @@ var BasicModel = AbstractModel.extend({
});
}
},
- /**
- * Every RPC done by the model need to add some context, which is a
- * combination of the context of the session, of the record/list, and/or of
- * the concerned field. This method combines all these contexts and evaluate
- * them with the proper evalcontext.
- *
- * @param {string} recordID - the id of an element from the localData
- * @see _getContext for the options parameter
- * @returns {Object} the evaluated context
- */
- getContext: function (recordID, options) {
- var record = this.localData[recordID];
- return this._getContext(record, options);
- },
//--------------------------------------------------------------------------
// Private
@@ -1626,25 +1616,22 @@ var BasicModel = AbstractModel.extend({
*/
_getContext: function (element, options) {
var context = new Context(session.user_context, element.context);
- var evalContext = this.get(element.id, {raw: true}).data;
- if (element.parentID) {
- var parent = this.get(element.parentID, {raw: true});
- if (parent.type === 'list' && this.localData[element.parentID].parentID) {
- parent = this.get(this.localData[element.parentID].parentID, {raw: true});
- }
- _.extend(evalContext, {parent: parent.data});
- }
- context.set_eval_context(evalContext);
+ context.set_eval_context(this._getEvalContext(element));
+
if (options && options.fieldName) {
var attrs = element.fieldAttrs[options.fieldName];
if (attrs && attrs.context) {
context.add(attrs.context);
+ } else {
+ var fieldParams = element.fields[options.fieldName];
+ if (fieldParams.context) {
+ context.add(fieldParams.context);
+ }
}
}
-
if (element.rawContext) {
var rawContext = new Context(element.rawContext);
- var evalContext = this.get(element.parentID, {raw: true}).data;
+ var evalContext = this._getEvalContext(this.localData[element.parentID]);
evalContext.id = evalContext.id || false;
rawContext.set_eval_context(evalContext);
context.add(rawContext);
@@ -1653,6 +1640,63 @@ var BasicModel = AbstractModel.extend({
return context.eval();
},
/**
+ * Some records are associated to a/some domain(s). This method allows to
+ * retrieve them, evaluated.
+ *
+ * @param {Object} element an element from the localData
+ * @param {Object} [options]
+ * @param {string} [options.fieldName]
+ * the name of the field whose domain needs to be returned
+ * @returns {Array} the evaluated domain
+ */
+ _getDomain: function (element, options) {
+ if (options && options.fieldName) {
+ var attrs = element.fieldAttrs[options.fieldName];
+ if (attrs && attrs.domain) {
+ return Domain.prototype.stringToArray(
+ attrs.domain,
+ this._getEvalContext(element)
+ );
+ }
+ var fieldParams = element.fields[options.fieldName];
+ if (fieldParams.domain) {
+ return Domain.prototype.stringToArray(
+ fieldParams.domain,
+ this._getEvalContext(element)
+ );
+ }
+ return [];
+ }
+
+ return Domain.prototype.stringToArray(
+ element.domain,
+ this._getEvalContext(element)
+ );
+ },
+ /**
+ * Returns the evaluation context that should be used when evaluating the
+ * context/domain associated to a given element from the localData.
+ *
+ * @param {Object} element - an element from the localData
+ * @returns {Object}
+ */
+ _getEvalContext: function (element) {
+ var evalContext = this.get(element.id, {raw: true}).data;
+ evalContext.active_model = element.model;
+ if (evalContext.id) {
+ evalContext.active_id = evalContext.id;
+ evalContext.active_ids = [evalContext.id];
+ }
+ if (element.parentID) {
+ var parent = this.get(element.parentID, {raw: true});
+ if (parent.type === 'list' && this.localData[element.parentID].parentID) {
+ parent = this.get(this.localData[element.parentID].parentID, {raw: true});
+ }
+ _.extend(evalContext, {parent: parent.data});
+ }
+ return evalContext;
+ },
+ /**
* Helper method for the load entry point.
*
* @see load
@@ -1759,8 +1803,7 @@ var BasicModel = AbstractModel.extend({
}
return val;
});
- var _domain = new data.CompoundDomain(field.domain || {}).set_eval_context(field_values);
- var domain = pyeval.eval('domain', _domain);
+ var domain = Domain.prototype.stringToArray(field.domain || [], field_values);
var fold_field = pyeval.py_eval(attrs.options || '{}').fold_field;
var fetch_status_information = self.rpc(field.relation, 'search_read')
.withFields(['id'].concat(fold_field ? [fold_field] : []))
diff --git a/addons/web/static/src/js/views/field_manager_mixin.js b/addons/web/static/src/js/views/field_manager_mixin.js
index 00607157a1f..4e61d25f468 100644
--- a/addons/web/static/src/js/views/field_manager_mixin.js
+++ b/addons/web/static/src/js/views/field_manager_mixin.js
@@ -18,7 +18,6 @@ var FieldManagerMixin = {
custom_events: {
field_changed: '_onFieldChanged',
load: '_onLoad',
- call_service: '_onCallService',
},
/**
* A FieldManagerMixin can be initialized with an instance of a basicModel.
@@ -114,40 +113,6 @@ var FieldManagerMixin = {
data.on_success(self.model.get(db_id));
});
},
- /**
- * Some widgets perform model RPCs. These are intercepted so that the
- * context can be automatically added according to the given dataPointID
- * option the AbstractField implementation automatically adds.
- *
- * @param {OdooEvent} e
- * @param {string} e.data.service - the service called, here we are only
- * interested in the "ajax" service
- * @param {string} e.data.method - the service method called, here we are
- * only interested by the "rpc" method
- * @param {Array} e.data.args
- * the args parameters contains the route, the route arguments and
- * the RPC call options. This method purpose is to check the RPC
- * call options for the dataPointID (and field name) and merge the
- * appropriate context with the route arguments' one.
- */
- _onCallService: function (e) {
- if (e.data.service !== "ajax" || e.data.method !== "rpc") {
- return;
- }
-
- var args = e.data.args[1];
- var options = e.data.args[2];
- if (args.kwargs && options.dataPointID) {
- args.kwargs.context = _.extend(
- this.model.getContext(options.dataPointID, {
- fieldName: options.fieldName || false,
- }),
- args.kwargs.context || {}
- );
- // to prevent doing this work again, in some other widget with FMM.
- delete options.dataPointID;
- }
- },
};
return FieldManagerMixin;
diff --git a/addons/web/static/src/js/views/form/form_controller.js b/addons/web/static/src/js/views/form/form_controller.js
index 0bbcdefb815..25cecf03822 100644
--- a/addons/web/static/src/js/views/form/form_controller.js
+++ b/addons/web/static/src/js/views/form/form_controller.js
@@ -243,7 +243,7 @@ var FormController = BasicController.extend({
var recordID = record.data.id;
this.trigger_up('execute_action', {
action_data: _.extend({}, attrs, {
- context: data.build_context(record, attrs.context)
+ context: _.extend(record.getContext(), attrs.context),
}),
model: record.model,
record_id: recordID,
@@ -532,7 +532,7 @@ var FormController = BasicController.extend({
res_model: model,
res_id: res_id,
fields_view: form_fields_view,
- context: data.build_context(record),
+ context: record.getContext(),
readonly: event.data.readonly,
title: _t("Open: ") + event.data.string,
on_save: event.data.on_save,
@@ -605,7 +605,7 @@ return FormController;
// record = record || this.model.get(this.db_id);
// var record_id = record.data.id;
// this.trigger_up('execute_action', {
- // action_data: _.extend({}, attrs, { context: data.build_context(record, attrs.context) }),
+ // action_data: _.extend({}, attrs, { context: _.extend(record.getContext(), attrs.context) }),
// dataset: this.dataset,
// record_id: record_id,
// on_close: function (reason) {
diff --git a/addons/web/static/test/core/pyeval_tests.js b/addons/web/static/test/core/pyeval_tests.js
index cde5193ce9d..76993bdfdfe 100644
--- a/addons/web/static/test/core/pyeval_tests.js
+++ b/addons/web/static/test/core/pyeval_tests.js
@@ -281,13 +281,13 @@ QUnit.module('core', function () {
}, /^Error: TypeError:/); //20
assert.throws(function () {
py.eval('a - 1', ctx);
- }, /^Error: TypeError:/);
+ }, /^Error: TypeError:/);
assert.throws(function () {
py.eval('1 + a', ctx);
- }, /^Error: TypeError:/);
+ }, /^Error: TypeError:/);
assert.throws(function () {
py.eval('1 - a', ctx);
- }, /^Error: TypeError:/);
+ }, /^Error: TypeError:/);
// delta - date is senseless.
assert.throws(function () {
@@ -451,7 +451,7 @@ QUnit.module('core', function () {
{ "active_id": 9, "active_ids": [ 9 ], "active_model": "mail.compose.message" }
];
var result = pyEval.eval_domains_and_contexts({
- contexts: ctx,
+ contexts: ctx,
domins: [],
});
@@ -522,10 +522,7 @@ QUnit.module('core', function () {
var result = pyEval.eval_domains_and_contexts({
domains: [
[['type', '=', 'contract']],
- { "__domains": [["|"], [["state", "in", ["open", "draft"]]], [["type", "=", "contract"], ["state", "=", "pending"]]],
- "__eval_context": null,
- "__ref": "compound_domain"
- },
+ ["|", ["state", "in", ["open", "draft"]], [["type", "=", "contract"], ["state", "=", "pending"]]],
"['|', '&', ('date', '!=', False), ('date', '<=', time.strftime('%Y-%m-%d')), ('is_overdue_quantity', '=', True)]",
[['user_id', '=', 1]]
],
@@ -562,14 +559,11 @@ QUnit.module('core', function () {
});
assert.deepEqual(result1.domain, [['company_id', '=', false]]);
- var compound_domain = new data.CompoundDomain(d);
- compound_domain.set_eval_context({company_id: 42});
-
var result2 = pyEval.eval_domains_and_contexts({
- domains: [compound_domain],
+ domains: [d],
contexts: [],
+ eval_context: {company_id: 42},
});
-
assert.deepEqual(result2.domain, [['company_id', '=', 42]]);
});
@@ -1014,4 +1008,3 @@ QUnit.module('core', function () {
});
});
-
diff --git a/addons/web/static/test/fields/relational_fields_tests.js b/addons/web/static/test/fields/relational_fields_tests.js
index af4a1d9ee18..3b5a051c768 100644
--- a/addons/web/static/test/fields/relational_fields_tests.js
+++ b/addons/web/static/test/fields/relational_fields_tests.js
@@ -437,8 +437,8 @@ QUnit.module('relational_fields', {
form.destroy();
});
- QUnit.test('check that the field context is used when doing a name_search in a m2o', function (assert) {
- assert.expect(3);
+ QUnit.test('domain and context are correctly used when doing a name_search in a m2o', function (assert) {
+ assert.expect(2);
var form = createView({
View: FormView,
@@ -446,20 +446,24 @@ QUnit.module('relational_fields', {
data: this.data,
arch:
'<form string="Partners">' +
- '<field name="product_id" context="{\'hello\': \'world\', \'test\': foo}"/>' +
+ '<field name="product_id" ' +
+ 'domain="[[\'foo\', \'=\', \'bar\'], [\'foo\', \'=\', foo]]" ' +
+ 'context="{\'hello\': \'world\', \'test\': foo}"/>' +
'<field name="foo"/>' +
'</form>',
res_id: 1,
session: {user_context: {hey: "ho"}},
mockRPC: function (route, args) {
if (route === '/web/dataset/call_kw/product/name_search') {
- var context = args.kwargs.context;
- assert.strictEqual(context.hey, "ho",
- 'the session context should have been used for the rpc');
- assert.strictEqual(context.hello, "world",
- 'the field context should have been used for the RPC');
- assert.strictEqual(context.test, "yop",
- 'the field context should have been properly evaluated');
+ assert.deepEqual(
+ args.kwargs.args,
+ [['foo', '=', 'bar'], ['foo', '=', 'yop']],
+ 'the field attr domain should have been used for the RPC (and evaluated)');
+ assert.deepEqual(
+ args.kwargs.context,
+ {hey: "ho", hello: "world", test: "yop"},
+ 'the field attr context should have been used for the '
+ + 'RPC (evaluated and merged with the session one)');
}
return this._super.apply(this, arguments);
},
diff --git a/addons/web/static/test/views/basic_model_tests.js b/addons/web/static/test/views/basic_model_tests.js
index 5fba6cd4444..0fb7876063d 100644
--- a/addons/web/static/test/views/basic_model_tests.js
+++ b/addons/web/static/test/views/basic_model_tests.js
@@ -638,7 +638,6 @@ QUnit.module('Views', {
model.destroy();
});
-
QUnit.test('write commands on a one2many', function (assert) {
assert.expect(4);
@@ -835,5 +834,64 @@ QUnit.module('Views', {
model.destroy();
});
+ QUnit.test('contexts and domains can be properly fetched', function (assert) {
+ assert.expect(8);
+
+ this.data.partner.fields.product_id.context = "{'hello': 'world', 'test': foo}";
+ this.data.partner.fields.product_id.domain = "[['hello', 'like', 'world'], ['test', 'like', foo]]";
+
+ var model = createModel({
+ Model: BasicModel,
+ data: this.data,
+ });
+
+ this.params.fieldNames = ['product_id', 'foo'];
+
+ model.load(this.params).then(function (resultID) {
+ var recordPartner = model.get(resultID);
+ assert.strictEqual(typeof recordPartner.getContext, "function",
+ "partner record should have a getContext function");
+ assert.strictEqual(typeof recordPartner.getDomain, "function",
+ "partner record should have a getDomain function");
+ assert.deepEqual(recordPartner.getContext(), {},
+ "asking for a context without a field name should fetch the session/user/view context");
+ assert.deepEqual(recordPartner.getDomain(), [],
+ "asking for a domain without a field name should fetch the session/user/view domain");
+ assert.deepEqual(
+ recordPartner.getContext({fieldName: "product_id"}),
+ {hello: "world", test: "gnap"},
+ "asking for a context with a field name should fetch the field context (evaluated)");
+ assert.deepEqual(
+ recordPartner.getDomain({fieldName: "product_id"}),
+ [["hello", "like", "world"], ["test", "like", "gnap"]],
+ "asking for a domain with a field name should fetch the field domain (evaluated)");
+ });
+ model.destroy();
+ // Try again with xml override of field domain and context
+ model = createModel({
+ Model: BasicModel,
+ data: this.data,
+ });
+
+ this.params.fieldAttrs = {
+ product_id: {
+ context: "{'hello2': 'world', 'test2': foo}",
+ domain: "[['hello2', 'like', 'world'], ['test2', 'like', foo]]",
+ },
+ };
+
+ model.load(this.params).then(function (resultID) {
+ var recordPartner = model.get(resultID);
+ assert.deepEqual(
+ recordPartner.getContext({fieldName: "product_id"}),
+ {hello2: "world", test2: "gnap"},
+ "field context should have been overriden by xml attribute");
+ assert.deepEqual(
+ recordPartner.getDomain({fieldName: "product_id"}),
+ [["hello2", "like", "world"], ["test2", "like", "gnap"]],
+ "field domain should have been overriden by xml attribute");
+ });
+ model.destroy();
+ });
});});