diff --git a/addons/mail/static/src/js/basic_view.js b/addons/mail/static/src/js/basic_view.js
index a019709dad4..a7ce716b460 100644
--- a/addons/mail/static/src/js/basic_view.js
+++ b/addons/mail/static/src/js/basic_view.js
@@ -6,10 +6,10 @@ var BasicView = require('web.BasicView');
var mailWidgets = ['mail_followers', 'mail_thread', 'mail_activity', 'kanban_activity'];
BasicView.include({
- init: function (viewInfo) {
+ init: function () {
this._super.apply(this, arguments);
this.mailFields = {};
- var fieldsInfo = viewInfo.fieldsInfo[this.viewType];
+ var fieldsInfo = this.fieldsInfo[this.viewType];
for (var fieldName in fieldsInfo) {
var fieldInfo = fieldsInfo[fieldName];
if (_.contains(mailWidgets, fieldInfo.widget)) {
diff --git a/addons/web/static/src/js/chrome/action_manager_act_window.js b/addons/web/static/src/js/chrome/action_manager_act_window.js
index 1410063dcca..56c6e45a08d 100644
--- a/addons/web/static/src/js/chrome/action_manager_act_window.js
+++ b/addons/web/static/src/js/chrome/action_manager_act_window.js
@@ -347,11 +347,13 @@ ActionManager.include({
_.each(action.views, function (view) {
var viewType = view[1];
var fieldsView = fieldsViews[viewType];
- var View = view_registry.get(fieldsView.arch.attrs.js_class || viewType);
+ var parsedXML = new DOMParser().parseFromString(fieldsView.arch, "text/xml");
+ var key = parsedXML.documentElement.getAttribute('js_class');
+ var View = view_registry.get(key || viewType);
if (View) {
views.push({
accessKey: View.prototype.accessKey,
- fieldsView: fieldsViews[viewType],
+ fieldsView: fieldsView,
icon: View.prototype.icon,
isMobileFriendly: View.prototype.mobile_friendly,
multiRecord: View.prototype.multi_record,
diff --git a/addons/web/static/src/js/chrome/search_view.js b/addons/web/static/src/js/chrome/search_view.js
index 6fbda215814..d1b732477e6 100644
--- a/addons/web/static/src/js/chrome/search_view.js
+++ b/addons/web/static/src/js/chrome/search_view.js
@@ -9,6 +9,7 @@ var FilterMenu = require('web.FilterMenu');
var GroupByMenu = require('web.GroupByMenu');
var pyeval = require('web.pyeval');
var search_inputs = require('web.search_inputs');
+var utils = require('web.utils');
var Widget = require('web.Widget');
var _t = core._t;
@@ -274,7 +275,8 @@ var SearchView = Widget.extend({
this._super.apply(this, arguments);
this.options = options;
this.dataset = dataset;
- this.fields_view = fvg;
+ this.fields_view = this._processFieldsView(_.clone(fvg));
+
this.fields = this.fields_view.fields;
this.query = undefined;
this.title = this.options.action && this.options.action.name;
@@ -603,6 +605,26 @@ var SearchView = Widget.extend({
current_category = filter.category;
});
},
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * Processes a fieldsView in place. In particular, parses its arch.
+ *
+ * @todo: this function is also defined in AbstractView ; this code
+ * duplication could be removed once the SearchView will be rewritten.
+ * @private
+ * @param {Object} fv
+ * @param {string} fv.arch
+ * @returns {Object} the processed fieldsView
+ */
+ _processFieldsView: function (fv) {
+ var doc = $.parseXML(fv.arch).documentElement;
+ fv.arch = utils.xml_to_json(doc, true);
+ return fv;
+ },
});
_.extend(SearchView, {
diff --git a/addons/web/static/src/js/services/data_manager.js b/addons/web/static/src/js/services/data_manager.js
index 72ec816efdd..bb7f25d206b 100644
--- a/addons/web/static/src/js/services/data_manager.js
+++ b/addons/web/static/src/js/services/data_manager.js
@@ -1,10 +1,7 @@
odoo.define('web.DataManager', function (require) {
"use strict";
-var config = require('web.config');
var core = require('web.core');
-var fieldRegistry = require('web.field_registry');
-var pyeval = require('web.pyeval');
var rpc = require('web.rpc');
var utils = require('web.utils');
@@ -100,13 +97,18 @@ return core.Class.extend({
model: model,
method: 'load_views',
}).then(function (result) {
- // Postprocess fields_views and insert them into the fields_views cache
- result.fields_views = _.mapObject(result.fields_views, self._postprocess_fvg.bind(self));
- self.processViews(result.fields_views, result.fields);
+ // Freeze the fields dict as it will be shared between views and
+ // no one should edit it
+ utils.deepFreeze(result.fields);
+
+ // Insert views into the fields_views cache
_.each(views_descr, function (view_descr) {
var toolbar = options.toolbar && view_descr[1] !== 'search';
var fv_key = self._gen_key(model, view_descr[0], view_descr[1], toolbar, context);
- self._cache.fields_views[fv_key] = $.when(result.fields_views[view_descr[1]]);
+ var fvg = result.fields_views[view_descr[1]];
+ fvg.viewFields = fvg.fields;
+ fvg.fields = result.fields;
+ self._cache.fields_views[fv_key] = $.when(fvg);
});
// Insert filters, if any, into the filters cache
@@ -184,57 +186,6 @@ return core.Class.extend({
});
},
- /**
- * Processes fields and fields_views. For each field, writes its name inside
- * the field description to make it self-contained. For each fields_view,
- * completes its fields with the missing ones.
- *
- * @param {Object} fieldsViews object of fields_views (keys are view types)
- * @param {Object} fields all the fields of the model
- */
- processViews: function (fieldsViews, fields) {
- var fieldName, fieldsView, viewType;
- // write the field name inside the description for all fields
- for (fieldName in fields) {
- fields[fieldName].name = fieldName;
- }
- for (viewType in fieldsViews) {
- fieldsView = fieldsViews[viewType];
- // write the field name inside the description for fields in view
- for (fieldName in fieldsView.fields) {
- fieldsView.fields[fieldName].name = fieldName;
- }
- // complete fields (in view) with missing ones
- _.defaults(fieldsView.fields, fields);
- // process the fields_view
- _.extend(fieldsView, this._processFieldsView({
- type: viewType,
- arch: fieldsView.arch,
- fields: fieldsView.fields,
- }));
- }
- },
-
- /**
- * Private function that postprocesses fields_view (mainly parses the arch attribute)
- */
- _postprocess_fvg: function (fields_view) {
- var self = this;
-
- // Parse arch
- var doc = $.parseXML(fields_view.arch).documentElement;
- fields_view.arch = utils.xml_to_json(doc, (doc.nodeName.toLowerCase() !== 'kanban'));
-
- // Process inner views (x2manys)
- _.each(fields_view.fields, function(field) {
- _.each(field.views || {}, function(view) {
- self._postprocess_fvg(view);
- });
- });
-
- return fields_view;
- },
-
/**
* Private function that generates a cache key from its arguments
*/
@@ -253,224 +204,6 @@ return core.Class.extend({
_invalidate: function (cache, key) {
delete cache[key];
},
-
- ///////////////////////////////////////////////////////////////
-
- /**
- * Process a field node, in particular, put a flag on the field to give
- * special directives to the BasicModel.
- *
- * @param {string} viewType
- * @param {Object} field - the field properties
- * @param {Object} attrs - the field attributes (from the xml)
- * @returns {Object} attrs
- */
- _processField: function (viewType, field, attrs) {
- var self = this;
- attrs.Widget = this._getFieldWidgetClass(viewType, field, attrs);
-
- if (!_.isObject(attrs.options)) { // parent arch could have already been processed (TODO this should not happen)
- attrs.options = attrs.options ? pyeval.py_eval(attrs.options) : {};
- }
-
- if (attrs.on_change && !field.onChange) {
- field.onChange = "1";
- }
-
- // the relational data of invisible relational fields should not be
- // fetched (e.g. name_gets of invisible many2ones), at least those that
- // are always invisible.
- // the invisible attribute of a field is supposed to be static ("1" in
- // general), but not totally as it may use keys of the context
- // ("context.get('some_key')"). It is evaluated server-side, and the
- // result is put inside the modifiers as a value of the '(column_)invisible'
- // key, and the raw value is left in the invisible attribute (it is used
- // in debug mode for informational purposes).
- // this should change, for instance the server might set the evaluated
- // value in invisible, which could then be seen as static by the client,
- // and add another key in debug mode containing the raw value.
- // for now, we look inside the modifiers and consider the value only if
- // it is static (=== true),
- if (attrs.modifiers.invisible === true || attrs.modifiers.column_invisible === true) {
- attrs.__no_fetch = true;
- }
-
- if (!_.isEmpty(field.views)) {
- // process the inner fields_view as well to find the fields they use.
- // register those fields' description directly on the view.
- // for those inner views, the list of all fields isn't necessary, so
- // basically the field_names will be the keys of the fields obj.
- // don't use _ to iterate on fields in case there is a 'length' field,
- // as _ doesn't behave correctly when there is a length key in the object
- attrs.views = {};
- _.each(field.views, function (innerFieldsView, viewType) {
- viewType = viewType === 'tree' ? 'list' : viewType;
- innerFieldsView.type = viewType;
- attrs.views[viewType] = self._processFieldsView(_.extend({}, innerFieldsView));
- });
- delete field.views;
- }
-
- if (field.type === 'one2many' || field.type === 'many2many') {
- if (attrs.Widget.prototype.useSubview) {
- if (!attrs.views) {
- attrs.views = {};
- }
- var mode = attrs.mode;
- if (!mode) {
- if (attrs.views.tree && attrs.views.kanban) {
- mode = 'tree';
- } else if (!attrs.views.tree && attrs.views.kanban) {
- mode = 'kanban';
- } else {
- mode = 'tree,kanban';
- }
- }
- if (mode.indexOf(',') !== -1) {
- mode = config.device.size_class !== config.device.SIZES.XS ? 'tree' : 'kanban';
- }
- if (mode === 'tree') {
- mode = 'list';
- if (!attrs.views.list && attrs.views.tree) {
- attrs.views.list = attrs.views.tree;
- }
- }
- attrs.mode = mode;
- if (mode in attrs.views) {
- var view = attrs.views[mode];
- var defaultOrder = view.arch.attrs.default_order;
- if (defaultOrder) {
- // process the default_order, which is like 'name,id desc'
- // but we need it like [{name: 'name', asc: true}, {name: 'id', asc: false}]
- attrs.orderedBy = _.map(defaultOrder.split(','), function (order) {
- order = order.trim().split(' ');
- return {name: order[0], asc: order[1] !== 'desc'};
- });
- } else {
- // if there is a field with widget `handle`, the x2many
- // needs to be ordered by this field to correctly display
- // the records
- var handleField = _.find(view.arch.children, function (child) {
- return child.attrs && child.attrs.widget === 'handle';
- });
- if (handleField) {
- attrs.orderedBy = [{name: handleField.attrs.name, asc: true}];
- }
- }
-
- attrs.columnInvisibleFields = {};
- _.each(view.arch.children, function (child) {
- if (child.attrs && child.attrs.modifiers) {
- attrs.columnInvisibleFields[child.attrs.name] =
- child.attrs.modifiers.column_invisible || false;
- }
- });
- }
- }
- if (attrs.Widget.prototype.fieldsToFetch) {
- attrs.viewType = 'default';
- attrs.relatedFields = _.extend({}, attrs.Widget.prototype.fieldsToFetch);
- attrs.fieldsInfo = {
- default: _.mapObject(attrs.Widget.prototype.fieldsToFetch, function () {
- return {};
- }),
- };
- if (attrs.options.color_field) {
- // used by m2m tags
- attrs.relatedFields[attrs.options.color_field] = { type: 'integer' };
- attrs.fieldsInfo.default[attrs.options.color_field] = {};
- }
- }
- }
-
- if (attrs.Widget.prototype.fieldDependencies) {
- attrs.fieldDependencies = attrs.Widget.prototype.fieldDependencies;
- }
-
- return attrs;
- },
- /**
- * Visit all nodes in the arch field and process each fields
- *
- * @param {string} viewType
- * @param {Object} arch
- * @param {Object} fields
- * @returns {Object} fieldsInfo
- */
- _processFields: function (viewType, arch, fields) {
- var self = this;
- var fieldsInfo = Object.create(null);
- utils.traverse(arch, function (node) {
- if (typeof node === 'string') {
- return false;
- }
- if (!_.isObject(node.attrs.modifiers)) {
- node.attrs.modifiers = node.attrs.modifiers ? JSON.parse(node.attrs.modifiers) : {};
- }
- if (!_.isObject(node.attrs.options) && node.tag === 'button') {
- node.attrs.options = node.attrs.options ? JSON.parse(node.attrs.options) : {};
- }
- if (node.tag === 'field') {
- fieldsInfo[node.attrs.name] = self._processField(viewType,
- fields[node.attrs.name], node.attrs ? _.clone(node.attrs) : {});
-
- if (fieldsInfo[node.attrs.name].fieldDependencies) {
- var deps = fieldsInfo[node.attrs.name].fieldDependencies;
- for (var dependency_name in deps) {
- var dependency_dict = {name: dependency_name, type: deps[dependency_name].type};
- if (!(dependency_name in fieldsInfo)) {
- fieldsInfo[dependency_name] = _.extend({}, dependency_dict, {options: deps[dependency_name].options || {}});
- }
- if (!(dependency_name in fields)) {
- fields[dependency_name] = dependency_dict;
- }
- }
- }
- return false;
- }
- return node.tag !== 'arch';
- });
- return fieldsInfo;
- },
- /**
- * Visit all nodes in the arch field and process each fields and inner views
- *
- * @param {Object} viewInfo
- * @param {Object} viewInfo.arch
- * @param {Object} viewInfo.fields
- * @returns {Object} viewInfo
- */
- _processFieldsView: function (viewInfo) {
- var viewFields = this._processFields(viewInfo.type, viewInfo.arch, viewInfo.fields);
- viewInfo.fieldsInfo = {};
- viewInfo.fieldsInfo[viewInfo.type] = viewFields;
- utils.deepFreeze(viewInfo.fields);
- return viewInfo;
- },
- /**
- * Returns the AbstractField specialization that should be used for the
- * given field informations. If there is no mentioned specific widget to
- * use, determine one according the field type.
- *
- * @param {string} viewType
- * @param {Object} field
- * @param {Object} attrs
- * @returns {function|null} AbstractField specialization Class
- */
- _getFieldWidgetClass: function (viewType, field, attrs) {
- var Widget;
- if (attrs.widget) {
- Widget = fieldRegistry.getAny([viewType + "." + attrs.widget, attrs.widget]);
- if (!Widget) {
- console.warn("Missing widget: ", attrs.widget, " for field", attrs.name, "of type", field.type);
- }
- } else if (viewType === 'kanban' && field.type === 'many2many') {
- // we want to display the widget many2manytags in kanban even if it
- // is not specified in the view
- Widget = fieldRegistry.get('kanban.many2many_tags');
- }
- return Widget || fieldRegistry.getAny([viewType + "." + field.type, field.type, "abstract"]);
- },
});
});
diff --git a/addons/web/static/src/js/views/abstract_view.js b/addons/web/static/src/js/views/abstract_view.js
index 6dc7be88baa..a696f05bffe 100644
--- a/addons/web/static/src/js/views/abstract_view.js
+++ b/addons/web/static/src/js/views/abstract_view.js
@@ -28,6 +28,7 @@ var Class = require('web.Class');
var AbstractModel = require('web.AbstractModel');
var AbstractRenderer = require('web.AbstractRenderer');
var AbstractController = require('web.AbstractController');
+var utils = require('web.utils');
var AbstractView = Class.extend({
// name displayed in view switchers
@@ -85,18 +86,32 @@ var AbstractView = Class.extend({
* @param {boolean} [params.action.views]
*/
init: function (viewInfo, params) {
+ // in general, the fieldsView has to be processed by the View (e.g. the
+ // arch is a string that needs to be parsed) ; the only exception is for
+ // inline form views inside form views, as they are processed alongside
+ // the main view, but they are opened in a FormViewDialog which
+ // instantiates another FormView (unlike kanban or list subviews for
+ // which only a Renderer is instantiated)
+ if (typeof viewInfo.arch === 'string') {
+ this.fieldsView = this._processFieldsView(viewInfo);
+ } else {
+ this.fieldsView = viewInfo;
+ }
+ this.fields = this.fieldsView.viewFields;
+ this.arch = this.fieldsView.arch;
+
this.rendererParams = {
- arch: viewInfo.arch,
+ arch: this.arch,
noContentHelp: params.action && params.action.help,
};
this.controllerParams = {
modelName: params.modelName,
activeActions: {
- edit: viewInfo.arch.attrs.edit ? JSON.parse(viewInfo.arch.attrs.edit) : true,
- create: viewInfo.arch.attrs.create ? JSON.parse(viewInfo.arch.attrs.create) : true,
- delete: viewInfo.arch.attrs.delete ? JSON.parse(viewInfo.arch.attrs.delete) : true,
- duplicate: viewInfo.arch.attrs.duplicate ? JSON.parse(viewInfo.arch.attrs.duplicate) : true,
+ edit: this.arch.attrs.edit ? JSON.parse(this.arch.attrs.edit) : true,
+ create: this.arch.attrs.create ? JSON.parse(this.arch.attrs.create) : true,
+ delete: this.arch.attrs.delete ? JSON.parse(this.arch.attrs.delete) : true,
+ duplicate: this.arch.attrs.duplicate ? JSON.parse(this.arch.attrs.duplicate) : true,
},
groupable: this.groupable,
controllerID: params.controllerID,
@@ -134,7 +149,7 @@ var AbstractView = Class.extend({
// 'name,id desc'
// but we need it like:
// [{name: 'id', asc: false}, {name: 'name', asc: true}]
- var defaultOrder = viewInfo.arch.attrs.default_order;
+ var defaultOrder = this.arch.attrs.default_order;
if (defaultOrder) {
this.loadParams.orderedBy = _.map(defaultOrder.split(','), function (order) {
order = order.trim().split(' ');
@@ -235,6 +250,22 @@ var AbstractView = Class.extend({
var model = this.getModel(parent);
return model.load(this.loadParams);
},
+ /**
+ * Processes a fieldsView. In particular, parses its arch.
+ *
+ * @private
+ * @param {Object} fieldsView
+ * @param {string} fieldsView.arch
+ * @returns {Object} the processed fieldsView
+ */
+ _processFieldsView: function (fieldsView) {
+ var fv = _.extend({}, fieldsView);
+ var doc = $.parseXML(fv.arch).documentElement;
+ var stripWhitespaces = doc.nodeName.toLowerCase() !== 'kanban';
+ fv.arch = utils.xml_to_json(doc, stripWhitespaces);
+ fv.viewFields = _.defaults({}, fv.viewFields, fv.fields);
+ return fv;
+ },
});
return AbstractView;
diff --git a/addons/web/static/src/js/views/basic/basic_view.js b/addons/web/static/src/js/views/basic/basic_view.js
index 8a91e2b90c7..f428a83cd41 100644
--- a/addons/web/static/src/js/views/basic/basic_view.js
+++ b/addons/web/static/src/js/views/basic/basic_view.js
@@ -13,6 +13,10 @@ odoo.define('web.BasicView', function (require) {
var AbstractView = require('web.AbstractView');
var BasicController = require('web.BasicController');
var BasicModel = require('web.BasicModel');
+var config = require('web.config');
+var fieldRegistry = require('web.field_registry');
+var pyeval = require('web.pyeval');
+var utils = require('web.utils');
var BasicView = AbstractView.extend({
config: _.extend({}, AbstractView.prototype.config, {
@@ -35,6 +39,9 @@ var BasicView = AbstractView.extend({
init: function (viewInfo, params) {
this._super.apply(this, arguments);
+ this.fieldsInfo = {};
+ this.fieldsInfo[this.viewType] = this.fieldsView.fieldsInfo[this.viewType];
+
this.rendererParams.viewType = this.viewType;
this.controllerParams.confirmOnDelete = true;
@@ -42,10 +49,10 @@ var BasicView = AbstractView.extend({
this.controllerParams.hasButtons =
'action_buttons' in params ? params.action_buttons : true;
- this.loadParams.fieldsInfo = viewInfo.fieldsInfo;
- this.loadParams.fields = viewInfo.fields;
+ this.loadParams.fieldsInfo = this.fieldsInfo;
+ this.loadParams.fields = this.fields;
this.loadParams.context = params.context || {};
- this.loadParams.limit = parseInt(viewInfo.arch.attrs.limit, 10) || params.limit;
+ this.loadParams.limit = parseInt(this.arch.attrs.limit, 10) || params.limit;
this.loadParams.viewType = this.viewType;
this.loadParams.parentID = params.parentID;
this.recordID = params.recordID;
@@ -57,6 +64,31 @@ var BasicView = AbstractView.extend({
// Private
//--------------------------------------------------------------------------
+ /**
+ * Returns the AbstractField specialization that should be used for the
+ * given field informations. If there is no mentioned specific widget to
+ * use, determines one according the field type.
+ *
+ * @private
+ * @param {string} viewType
+ * @param {Object} field
+ * @param {Object} attrs
+ * @returns {function|null} AbstractField specialization Class
+ */
+ _getFieldWidgetClass: function (viewType, field, attrs) {
+ var FieldWidget;
+ if (attrs.widget) {
+ FieldWidget = fieldRegistry.getAny([viewType + "." + attrs.widget, attrs.widget]);
+ if (!FieldWidget) {
+ console.warn("Missing widget: ", attrs.widget, " for field", attrs.name, "of type", field.type);
+ }
+ } else if (viewType === 'kanban' && field.type === 'many2many') {
+ // we want to display the widget many2manytags in kanban even if it
+ // is not specified in the view
+ FieldWidget = fieldRegistry.get('kanban.many2many_tags');
+ }
+ return FieldWidget || fieldRegistry.getAny([viewType + "." + field.type, field.type, "abstract"]);
+ },
/**
* In some cases, we already have a preloaded record
*
@@ -67,6 +99,15 @@ var BasicView = AbstractView.extend({
_loadData: function () {
if (this.recordID) {
var self = this;
+
+ // Add the fieldsInfo of the current view to the given recordID,
+ // as it will be shared between two views, and it must be able to
+ // handle changes on fields that are only on this view.
+ this.model.addFieldsInfo(this.recordID, {
+ fields: this.fields,
+ fieldsInfo: this.fieldsInfo,
+ });
+
var record = this.model.get(this.recordID);
var viewType = this.viewType;
var viewFields = Object.keys(record.fieldsInfo[viewType]);
@@ -135,6 +176,205 @@ var BasicView = AbstractView.extend({
}
return this._super.apply(this, arguments);
},
+ /**
+ * Processes a field node, in particular, put a flag on the field to give
+ * special directives to the BasicModel.
+ *
+ * @private
+ * @param {string} viewType
+ * @param {Object} field - the field properties
+ * @param {Object} attrs - the field attributes (from the xml)
+ * @returns {Object} attrs
+ */
+ _processField: function (viewType, field, attrs) {
+ var self = this;
+ attrs.Widget = this._getFieldWidgetClass(viewType, field, attrs);
+
+ if (!_.isObject(attrs.options)) { // parent arch could have already been processed (TODO this should not happen)
+ attrs.options = attrs.options ? pyeval.py_eval(attrs.options) : {};
+ }
+
+ if (attrs.on_change && !field.onChange) {
+ field.onChange = "1";
+ }
+
+ // the relational data of invisible relational fields should not be
+ // fetched (e.g. name_gets of invisible many2ones), at least those that
+ // are always invisible.
+ // the invisible attribute of a field is supposed to be static ("1" in
+ // general), but not totally as it may use keys of the context
+ // ("context.get('some_key')"). It is evaluated server-side, and the
+ // result is put inside the modifiers as a value of the '(column_)invisible'
+ // key, and the raw value is left in the invisible attribute (it is used
+ // in debug mode for informational purposes).
+ // this should change, for instance the server might set the evaluated
+ // value in invisible, which could then be seen as static by the client,
+ // and add another key in debug mode containing the raw value.
+ // for now, we look inside the modifiers and consider the value only if
+ // it is static (=== true),
+ if (attrs.modifiers.invisible === true || attrs.modifiers.column_invisible === true) {
+ attrs.__no_fetch = true;
+ }
+
+ if (!_.isEmpty(field.views)) {
+ // process the inner fields_view as well to find the fields they use.
+ // register those fields' description directly on the view.
+ // for those inner views, the list of all fields isn't necessary, so
+ // basically the field_names will be the keys of the fields obj.
+ // don't use _ to iterate on fields in case there is a 'length' field,
+ // as _ doesn't behave correctly when there is a length key in the object
+ attrs.views = {};
+ _.each(field.views, function (innerFieldsView, viewType) {
+ viewType = viewType === 'tree' ? 'list' : viewType;
+ attrs.views[viewType] = self._processFieldsView(innerFieldsView, viewType);
+ });
+ }
+
+ if (field.type === 'one2many' || field.type === 'many2many') {
+ if (attrs.Widget.prototype.useSubview) {
+ if (!attrs.views) {
+ attrs.views = {};
+ }
+ var mode = attrs.mode;
+ if (!mode) {
+ if (attrs.views.tree && attrs.views.kanban) {
+ mode = 'tree';
+ } else if (!attrs.views.tree && attrs.views.kanban) {
+ mode = 'kanban';
+ } else {
+ mode = 'tree,kanban';
+ }
+ }
+ if (mode.indexOf(',') !== -1) {
+ mode = config.device.size_class !== config.device.SIZES.XS ? 'tree' : 'kanban';
+ }
+ if (mode === 'tree') {
+ mode = 'list';
+ if (!attrs.views.list && attrs.views.tree) {
+ attrs.views.list = attrs.views.tree;
+ }
+ }
+ attrs.mode = mode;
+ if (mode in attrs.views) {
+ var view = attrs.views[mode];
+ var defaultOrder = view.arch.attrs.default_order;
+ if (defaultOrder) {
+ // process the default_order, which is like 'name,id desc'
+ // but we need it like [{name: 'name', asc: true}, {name: 'id', asc: false}]
+ attrs.orderedBy = _.map(defaultOrder.split(','), function (order) {
+ order = order.trim().split(' ');
+ return {name: order[0], asc: order[1] !== 'desc'};
+ });
+ } else {
+ // if there is a field with widget `handle`, the x2many
+ // needs to be ordered by this field to correctly display
+ // the records
+ var handleField = _.find(view.arch.children, function (child) {
+ return child.attrs && child.attrs.widget === 'handle';
+ });
+ if (handleField) {
+ attrs.orderedBy = [{name: handleField.attrs.name, asc: true}];
+ }
+ }
+
+ attrs.columnInvisibleFields = {};
+ _.each(view.arch.children, function (child) {
+ if (child.attrs && child.attrs.modifiers) {
+ attrs.columnInvisibleFields[child.attrs.name] =
+ child.attrs.modifiers.column_invisible || false;
+ }
+ });
+ }
+ }
+ if (attrs.Widget.prototype.fieldsToFetch) {
+ attrs.viewType = 'default';
+ attrs.relatedFields = _.extend({}, attrs.Widget.prototype.fieldsToFetch);
+ attrs.fieldsInfo = {
+ default: _.mapObject(attrs.Widget.prototype.fieldsToFetch, function () {
+ return {};
+ }),
+ };
+ if (attrs.options.color_field) {
+ // used by m2m tags
+ attrs.relatedFields[attrs.options.color_field] = { type: 'integer' };
+ attrs.fieldsInfo.default[attrs.options.color_field] = {};
+ }
+ }
+ }
+
+ if (attrs.Widget.prototype.fieldDependencies) {
+ attrs.fieldDependencies = attrs.Widget.prototype.fieldDependencies;
+ }
+
+ return attrs;
+ },
+ /**
+ * Visits all nodes in the arch and processes each fields.
+ *
+ * @private
+ * @param {string} viewType
+ * @param {Object} arch
+ * @param {Object} fields
+ * @returns {Object} fieldsInfo
+ */
+ _processFields: function (viewType, arch, fields) {
+ var self = this;
+ var fieldsInfo = Object.create(null);
+ utils.traverse(arch, function (node) {
+ if (typeof node === 'string') {
+ return false;
+ }
+ if (!_.isObject(node.attrs.modifiers)) {
+ node.attrs.modifiers = node.attrs.modifiers ? JSON.parse(node.attrs.modifiers) : {};
+ }
+ if (!_.isObject(node.attrs.options) && node.tag === 'button') {
+ node.attrs.options = node.attrs.options ? JSON.parse(node.attrs.options) : {};
+ }
+ if (node.tag === 'field') {
+ fieldsInfo[node.attrs.name] = self._processField(viewType,
+ fields[node.attrs.name], node.attrs ? _.clone(node.attrs) : {});
+
+ if (fieldsInfo[node.attrs.name].fieldDependencies) {
+ var deps = fieldsInfo[node.attrs.name].fieldDependencies;
+ for (var dependency_name in deps) {
+ var dependency_dict = {name: dependency_name, type: deps[dependency_name].type};
+ if (!(dependency_name in fieldsInfo)) {
+ fieldsInfo[dependency_name] = _.extend({}, dependency_dict, {options: deps[dependency_name].options || {}});
+ }
+ if (!(dependency_name in fields)) {
+ fields[dependency_name] = dependency_dict;
+ }
+ }
+ }
+ return false;
+ }
+ return node.tag !== 'arch';
+ });
+ return fieldsInfo;
+ },
+ /**
+ * Overrides to process the fields, and generate fieldsInfo which contains
+ * the description of the fields in view, with their attrs in the arch.
+ *
+ * @override
+ * @private
+ * @param {Object} fieldsView
+ * @param {string} fieldsView.arch
+ * @param {Object} fieldsView.fields
+ * @param {string} [viewType] by default, this.viewType
+ * @returns {Object} the processed fieldsView with extra key 'fieldsInfo'
+ */
+ _processFieldsView: function (fieldsView, viewType) {
+ var fv = this._super.apply(this, arguments);
+
+ viewType = viewType || this.viewType;
+ var viewFields = this._processFields(viewType, fv.arch, fv.viewFields);
+ fv.fieldsInfo = {};
+ fv.fieldsInfo[viewType] = viewFields;
+ fv.type = viewType;
+
+ return fv;
+ },
});
return BasicView;
diff --git a/addons/web/static/src/js/views/calendar/calendar_view.js b/addons/web/static/src/js/views/calendar/calendar_view.js
index 6a8dc5e106e..217f6240711 100644
--- a/addons/web/static/src/js/views/calendar/calendar_view.js
+++ b/addons/web/static/src/js/views/calendar/calendar_view.js
@@ -32,8 +32,8 @@ var CalendarView = AbstractView.extend({
groupable: false,
init: function (viewInfo, params) {
this._super.apply(this, arguments);
- var arch = viewInfo.arch;
- var fields = viewInfo.fields;
+ var arch = this.arch;
+ var fields = this.fields;
var attrs = arch.attrs;
if (!attrs.date_start) {
diff --git a/addons/web/static/src/js/views/form/form_view.js b/addons/web/static/src/js/views/form/form_view.js
index 548dfdc9624..10fc9a12b20 100644
--- a/addons/web/static/src/js/views/form/form_view.js
+++ b/addons/web/static/src/js/views/form/form_view.js
@@ -90,7 +90,8 @@ var FormView = BasicView.extend({
[[null, attrs.mode === 'tree' ? 'list' : attrs.mode]])
.then(function (views) {
for (var viewName in views) {
- attrs.views[viewName] = views[viewName];
+ // clone to make runbot green?
+ attrs.views[viewName] = self._processFieldsView(views[viewName], viewName);
}
self._setSubViewLimit(attrs);
}));
diff --git a/addons/web/static/src/js/views/graph/graph_view.js b/addons/web/static/src/js/views/graph/graph_view.js
index 44e1f74e8da..feb151b02e7 100644
--- a/addons/web/static/src/js/views/graph/graph_view.js
+++ b/addons/web/static/src/js/views/graph/graph_view.js
@@ -36,13 +36,13 @@ var GraphView = AbstractView.extend({
/**
* @override
*/
- init: function (viewInfo) {
+ init: function () {
this._super.apply(this, arguments);
var measure;
var groupBys = [];
- viewInfo.fields = _.defaults({__count__: {string: _t("Count"), type: "integer"}}, viewInfo.fields);
- viewInfo.arch.children.forEach(function (field) {
+ this.fields.__count__ = {string: _t("Count"), type: "integer"};
+ this.arch.children.forEach(function (field) {
var name = field.attrs.name;
if (field.attrs.interval) {
name += ':' + field.attrs.interval;
@@ -55,7 +55,7 @@ var GraphView = AbstractView.extend({
});
var measures = {__count__: {string: _t("Count"), type: "integer"}};
- _.each(viewInfo.fields, function (field, name) {
+ _.each(this.fields, function (field, name) {
if (name !== 'id' && field.store === true) {
if (field.type === 'integer' || field.type === 'float' || field.type === 'monetary') {
measures[name] = field;
@@ -64,12 +64,12 @@ var GraphView = AbstractView.extend({
});
this.controllerParams.measures = measures;
- this.rendererParams.stacked = viewInfo.arch.attrs.stacked !== "False";
+ this.rendererParams.stacked = this.arch.attrs.stacked !== "False";
- this.loadParams.mode = viewInfo.arch.attrs.type || 'bar';
+ this.loadParams.mode = this.arch.attrs.type || 'bar';
this.loadParams.measure = measure || '__count__';
this.loadParams.groupBys = groupBys || [];
- this.loadParams.fields = viewInfo.fields;
+ this.loadParams.fields = this.fields;
},
});
diff --git a/addons/web/static/src/js/views/kanban/kanban_view.js b/addons/web/static/src/js/views/kanban/kanban_view.js
index 730b761b870..87b4a366502 100644
--- a/addons/web/static/src/js/views/kanban/kanban_view.js
+++ b/addons/web/static/src/js/views/kanban/kanban_view.js
@@ -30,16 +30,14 @@ var KanbanView = BasicView.extend({
init: function (viewInfo, params) {
this._super.apply(this, arguments);
- var arch = viewInfo.arch;
-
this.loadParams.limit = this.loadParams.limit || 40;
// in mobile, columns are lazy-loaded, so set 'openGroupByDefault' to
// false so that they will won't be loaded by the initial load
this.loadParams.openGroupByDefault = config.device.isMobile ? false : true;
this.loadParams.type = 'list';
- this.loadParams.groupBy = arch.attrs.default_group_by ? [arch.attrs.default_group_by] : (params.groupBy || []);
+ this.loadParams.groupBy = this.arch.attrs.default_group_by ? [this.arch.attrs.default_group_by] : (params.groupBy || []);
var progressBar;
- utils.traverse(arch, function (n) {
+ utils.traverse(this.arch, function (n) {
var isProgressBar = (n.tag === 'progressbar');
if (isProgressBar) {
progressBar = _.clone(n.attrs);
@@ -54,16 +52,16 @@ var KanbanView = BasicView.extend({
var activeActions = this.controllerParams.activeActions;
activeActions = _.extend(activeActions, {
- group_create: arch.attrs.group_create ? JSON.parse(arch.attrs.group_create) : true,
- group_edit: arch.attrs.group_edit ? JSON.parse(arch.attrs.group_edit) : true,
- group_delete: arch.attrs.group_delete ? JSON.parse(arch.attrs.group_delete) : true,
+ group_create: this.arch.attrs.group_create ? JSON.parse(this.arch.attrs.group_create) : true,
+ group_edit: this.arch.attrs.group_edit ? JSON.parse(this.arch.attrs.group_edit) : true,
+ group_delete: this.arch.attrs.group_delete ? JSON.parse(this.arch.attrs.group_delete) : true,
});
this.rendererParams.column_options = {
editable: activeActions.group_edit,
deletable: activeActions.group_delete,
group_creatable: activeActions.group_create && !config.device.isMobile,
- quick_create: params.isQuickCreateEnabled || this._isQuickCreateEnabled(viewInfo),
+ quick_create: params.isQuickCreateEnabled || this._isQuickCreateEnabled(),
hasProgressBar: !!progressBar,
};
this.rendererParams.record_options = {
@@ -72,7 +70,7 @@ var KanbanView = BasicView.extend({
read_only_mode: params.readOnlyMode,
};
- this.controllerParams.on_create = arch.attrs.on_create;
+ this.controllerParams.on_create = this.arch.attrs.on_create;
this.controllerParams.readOnlyMode = false;
this.controllerParams.hasButtons = true;
@@ -90,17 +88,17 @@ var KanbanView = BasicView.extend({
* @private
* @param {Object} viewInfo
*/
- _isQuickCreateEnabled: function (viewInfo) {
+ _isQuickCreateEnabled: function () {
var groupBy = this.loadParams.groupBy[0];
groupBy = groupBy !== undefined ? groupBy.split(':')[0] : undefined;
- if (groupBy !== undefined && !_.contains(['char', 'boolean', 'many2one'], viewInfo.fields[groupBy].type)) {
+ if (groupBy !== undefined && !_.contains(['char', 'boolean', 'many2one'], this.fields[groupBy].type)) {
return false;
}
if (!this.controllerParams.activeActions.create) {
return false;
}
- if (viewInfo.arch.attrs.quick_create !== undefined) {
- return JSON.parse(viewInfo.arch.attrs.quick_create);
+ if (this.arch.attrs.quick_create !== undefined) {
+ return JSON.parse(this.arch.attrs.quick_create);
}
return true;
},
diff --git a/addons/web/static/src/js/views/list/list_view.js b/addons/web/static/src/js/views/list/list_view.js
index e970f825846..3082312e770 100644
--- a/addons/web/static/src/js/views/list/list_view.js
+++ b/addons/web/static/src/js/views/list/list_view.js
@@ -36,19 +36,18 @@ var ListView = BasicView.extend({
init: function (viewInfo, params) {
this._super.apply(this, arguments);
- var arch = viewInfo.arch;
- var mode = arch.attrs.editable && !params.readonly ? "edit" : "readonly";
+ var mode = this.arch.attrs.editable && !params.readonly ? "edit" : "readonly";
- this.controllerParams.editable = arch.attrs.editable;
+ this.controllerParams.editable = this.arch.attrs.editable;
this.controllerParams.hasSidebar = params.hasSidebar;
this.controllerParams.toolbarActions = viewInfo.toolbar;
this.controllerParams.noLeaf = !!this.loadParams.context.group_by_no_leaf;
this.controllerParams.mode = mode;
- this.rendererParams.arch = arch;
+ this.rendererParams.arch = this.arch;
this.rendererParams.hasSelectors =
'hasSelectors' in params ? params.hasSelectors : true;
- this.rendererParams.editable = params.readonly ? false : arch.attrs.editable;
+ this.rendererParams.editable = params.readonly ? false : this.arch.attrs.editable;
this.loadParams.limit = this.loadParams.limit || 80;
this.loadParams.type = 'list';
diff --git a/addons/web/static/src/js/views/pivot/pivot_view.js b/addons/web/static/src/js/views/pivot/pivot_view.js
index fbafd0e867c..0e908f29b8c 100644
--- a/addons/web/static/src/js/views/pivot/pivot_view.js
+++ b/addons/web/static/src/js/views/pivot/pivot_view.js
@@ -35,10 +35,6 @@ var PivotView = AbstractView.extend({
init: function (viewInfo, params) {
this._super.apply(this, arguments);
- var arch = viewInfo.arch;
- var fields = _.extend({
- __count: {string: _t("Count"), type: "integer"}
- }, viewInfo.fields);
var activeMeasures = [];
var colGroupBys = [];
var rowGroupBys = [];
@@ -46,7 +42,8 @@ var PivotView = AbstractView.extend({
var measures = {};
var groupableFields = {};
- _.each(fields, function (field, name) {
+ this.fields.__count = {string: _t("Count"), type: "integer"};
+ _.each(this.fields, function (field, name) {
if ((name !== 'id') && (field.store === true)) {
if (_.contains(['integer', 'float', 'monetary'], field.type)) {
measures[name] = field;
@@ -58,7 +55,7 @@ var PivotView = AbstractView.extend({
});
measures.__count = {string: _t("Count"), type: "integer"};
- arch.children.forEach(function (field) {
+ this.arch.children.forEach(function (field) {
var name = field.attrs.name;
if (field.attrs.interval) {
name += ':' + field.attrs.interval;
@@ -84,17 +81,17 @@ var PivotView = AbstractView.extend({
rowGroupBys.push(name);
}
});
- if ((!activeMeasures.length) || arch.attrs.display_quantity) {
+ if ((!activeMeasures.length) || this.arch.attrs.display_quantity) {
activeMeasures.push('__count');
}
this.loadParams.measures = activeMeasures;
this.loadParams.colGroupBys = colGroupBys;
this.loadParams.rowGroupBys = rowGroupBys;
- this.loadParams.fields = fields;
+ this.loadParams.fields = this.fields;
- this.controllerParams.title = params.title || arch.attrs.string || _t("Untitled");
- this.controllerParams.enableLinking = !arch.attrs.disable_linking;
+ this.controllerParams.title = params.title || this.arch.attrs.string || _t("Untitled");
+ this.controllerParams.enableLinking = !this.arch.attrs.disable_linking;
this.controllerParams.measures = measures;
this.controllerParams.groupableFields = groupableFields;
// retrieve form and list view ids from the action to open those views
diff --git a/addons/web/static/src/js/views/view_dialogs.js b/addons/web/static/src/js/views/view_dialogs.js
index 72e215f9c24..e45516acb37 100644
--- a/addons/web/static/src/js/views/view_dialogs.js
+++ b/addons/web/static/src/js/views/view_dialogs.js
@@ -165,9 +165,6 @@ var FormViewDialog = ViewDialog.extend({
}
fields_view_def.then(function (viewInfo) {
- if (self.recordID) {
- self.model.addFieldsInfo(self.recordID, viewInfo);
- }
var formview = new FormView(viewInfo, {
modelName: self.res_model,
context: self.context,
diff --git a/addons/web/static/tests/helpers/mock_server.js b/addons/web/static/tests/helpers/mock_server.js
index dbe1a2c49e0..8a44e1eb1e7 100644
--- a/addons/web/static/tests/helpers/mock_server.js
+++ b/addons/web/static/tests/helpers/mock_server.js
@@ -2,10 +2,8 @@ odoo.define('web.MockServer', function (require) {
"use strict";
var Class = require('web.Class');
-var data_manager = require('web.data_manager');
var Domain = require('web.Domain');
var pyeval = require('web.pyeval');
-var utils = require('web.utils');
var MockServer = Class.extend({
/**
@@ -56,6 +54,15 @@ var MockServer = Class.extend({
// Public
//--------------------------------------------------------------------------
+ /**
+ * Mocks a fields_get RPC for a given model.
+ *
+ * @param {string} model
+ * @returns {Object}
+ */
+ fieldsGet: function (model) {
+ return this.data[model].fields;
+ },
/**
* helper: read a string describing an arch, and returns a simulated
* 'field_view_get' call to the server. Calls processViews() of data_manager
@@ -77,13 +84,10 @@ var MockServer = Class.extend({
}
var fields = $.extend(true, {}, this.data[model].fields);
var fvg = this._fieldsViewGet(params.arch, model, fields, viewOptions.context);
- var fields_views = {};
- fields_views[fvg.type] = fvg;
- data_manager.processViews(fields_views, fields);
if (toolbar) {
fvg.toolbar = toolbar;
}
- return fields_views[fvg.type];
+ return fvg;
},
/**
* Simulate a complete RPC call. This is the main method for this class.
@@ -166,7 +170,7 @@ var MockServer = Class.extend({
* 'fields_view_get' call to the server.
*
* @private
- * @param {string|Object} arch a string OR a parsed xml document
+ * @param {string} arch a string OR a parsed xml document
* @param {string} model a model name (that should be in this.data)
* @param {Object} fields
* @param {Object} context
@@ -179,29 +183,32 @@ var MockServer = Class.extend({
var onchanges = this.data[model].onchanges || {};
var fieldNodes = {};
+ var doc;
if (typeof arch === 'string') {
- var doc = $.parseXML(arch).documentElement;
- arch = utils.xml_to_json(doc, true);
+ doc = $.parseXML(arch).documentElement;
+ } else {
+ doc = arch;
}
- var inTreeView = (arch.tag === 'tree');
+ var inTreeView = (doc.tagName === 'tree');
- this._traverse(arch, function (node) {
- if (typeof node === "string") {
+ this._traverse(doc, function (node) {
+ if (node.nodeType === Node.TEXT_NODE) {
return false;
}
var modifiers = {};
- var isField = (node.tag === 'field');
+ var isField = (node.tagName === 'field');
if (isField) {
- fieldNodes[node.attrs.name] = node;
+ var fieldName = node.getAttribute('name');
+ fieldNodes[fieldName] = node;
// 'transfer_field_to_modifiers' simulation
- var field = fields[node.attrs.name];
+ var field = fields[fieldName];
if (!field) {
- throw new Error("Field " + node.attrs.name + " does not exist");
+ throw new Error("Field " + fieldName + " does not exist");
}
var defaultValues = {};
var stateExceptions = {};
@@ -209,7 +216,7 @@ var MockServer = Class.extend({
stateExceptions[attr] = [];
defaultValues[attr] = !!field[attr];
});
- _.each(field['states'] || {}, function (modifs, state) {
+ _.each(field.states || {}, function (modifs, state) {
_.each(modifs, function (modif) {
if (defaultValues[modif[0]] !== modif[1]) {
stateExceptions[modif[0]].append(state);
@@ -226,36 +233,42 @@ var MockServer = Class.extend({
}
// 'transfer_node_to_modifiers' simulation
- if (node.attrs.attrs) {
- var attrs = pyeval.py_eval(node.attrs.attrs);
+ var attrs = node.getAttribute('attrs');
+ if (attrs) {
+ attrs = pyeval.py_eval(attrs);
_.extend(modifiers, attrs);
- delete node.attrs.attrs;
+ node.removeAttribute('attrs');
}
- if (node.attrs.states) {
+
+ var states = node.getAttribute('states');
+ if (states) {
if (!modifiers.invisible) {
modifiers.invisible = [];
}
- modifiers.invisible.push(["state", "not in", node.attrs.states.split(",")]);
+ modifiers.invisible.push(["state", "not in", states.split(",")]);
}
_.each(modifiersNames, function (a) {
- if (node.attrs[a]) {
+ var mod = node.getAttribute(a);
+ if (mod) {
var pyevalContext = window.py.dict.fromJSON(context || {});
- var v = pyeval.py_eval(node.attrs[a], {context: pyevalContext}) ? true: false;
+ var v = pyeval.py_eval(mod, {context: pyevalContext}) ? true: false;
if (inTreeView && a === 'invisible') {
- modifiers['column_invisible'] = v;
+ modifiers.column_invisible = v;
} else if (v || !(a in modifiers) || !_.isArray(modifiers[a])) {
modifiers[a] = v;
}
}
});
- // 'transfer_modifiers_to_node' simulation
_.each(modifiersNames, function (a) {
if (a in modifiers && (!!modifiers[a] === false || (_.isArray(modifiers[a]) && !modifiers[a].length))) {
delete modifiers[a];
}
});
- node.attrs.modifiers = JSON.stringify(modifiers);
+
+ if (Object.keys(modifiers).length) {
+ node.setAttribute('modifiers', JSON.stringify(modifiers));
+ }
return !isField;
});
@@ -264,29 +277,34 @@ var MockServer = Class.extend({
_.each(fieldNodes, function (node, name) {
var field = fields[name];
if (field.type === "many2one" || field.type === "many2many") {
- node.attrs.can_create = node.attrs.can_create || "true";
- node.attrs.can_write = node.attrs.can_write || "true";
+ var canCreate = node.getAttribute('can_create');
+ node.setAttribute('can_create', canCreate || "true");
+ var canWrite = node.getAttribute('can_write');
+ node.setAttribute('can_write', canWrite || "true");
}
if (field.type === "one2many" || field.type === "many2many") {
field.views = {};
- _.each(node.children, function (children) {
+ _.each(node.childNodes, function (children) {
relModel = field.relation;
relFields = $.extend(true, {}, self.data[relModel].fields);
- field.views[children.tag] = self._fieldsViewGet(children, relModel,
+ field.views[children.tagName] = self._fieldsViewGet(children, relModel,
relFields, context);
});
}
// add onchanges
if (name in onchanges) {
- node.attrs.on_change="1";
+ node.setAttribute('on_change', "1");
}
});
+
+ var xmlSerializer = new XMLSerializer();
+ var processedArch = xmlSerializer.serializeToString(doc);
return {
- arch: arch,
+ arch: processedArch,
fields: _.pick(fields, _.keys(fieldNodes)),
model: model,
- type: arch.tag === 'tree' ? 'list' : arch.tag,
+ type: doc.tagName === 'tree' ? 'list' : doc.tagName,
};
},
/**
@@ -1040,7 +1058,7 @@ var MockServer = Class.extend({
_traverse: function (tree, f) {
var self = this;
if (f(tree)) {
- _.each(tree.children, function (c) { self._traverse(c, f); });
+ _.each(tree.childNodes, function (c) { self._traverse(c, f); });
}
},
/**
diff --git a/addons/web/static/tests/helpers/test_utils.js b/addons/web/static/tests/helpers/test_utils.js
index 850301fb981..260436ff152 100644
--- a/addons/web/static/tests/helpers/test_utils.js
+++ b/addons/web/static/tests/helpers/test_utils.js
@@ -126,6 +126,23 @@ var createActionManager = function (params) {
return actionManager;
};
+/**
+ * performs a fields_view_get, and mocks the postprocessing done by the
+ * data_manager to return an equivalent structure.
+ *
+ * @param {MockServer} server
+ * @param {Object} params
+ * @param {string} params.model
+ * @returns {Object} an object with 3 keys: arch, fields and viewFields
+ */
+function fieldsViewGet(server, params) {
+ var fieldsView = server.fieldsViewGet(params);
+ // mock the structure produced by the DataManager
+ fieldsView.viewFields = fieldsView.fields;
+ fieldsView.fields = server.fieldsGet(params.model);
+ return fieldsView;
+}
+
/**
* create a view synchronously. This method uses the createAsyncView method.
* Most views are synchronous, so the deferred can be resolved immediately and
@@ -189,8 +206,7 @@ function createAsyncView(params) {
// add mock environment: mock server, session, fieldviewget, ...
var mockServer = addMockEnvironment(widget, params);
- var viewInfo = mockServer.fieldsViewGet(params);
-
+ var viewInfo = fieldsViewGet(mockServer, params);
// create the view
var viewOptions = {
modelName: params.model || 'foo',
@@ -419,7 +435,7 @@ function addMockEnvironment(widget, params) {
model: event.data.modelName,
}).then(function (views) {
views = _.mapObject(views, function (viewParams) {
- return mockServer.fieldsViewGet(viewParams);
+ return fieldsViewGet(mockServer, viewParams);
});
event.data.on_success(views);
});
@@ -694,6 +710,7 @@ return $.when(
createParent: createParent,
createView: createView,
dragAndDrop: dragAndDrop,
+ fieldsViewGet: fieldsViewGet,
intercept: intercept,
observe: observe,
patch: patch,
diff --git a/addons/web/static/tests/views/calendar_tests.js b/addons/web/static/tests/views/calendar_tests.js
index 48a8faf4f3e..835aa628ced 100644
--- a/addons/web/static/tests/views/calendar_tests.js
+++ b/addons/web/static/tests/views/calendar_tests.js
@@ -114,36 +114,12 @@ QUnit.module('Views', {
'