diff options
| author | Christophe Matthieu <chm@odoo.com> | 2017-03-21 15:30:58 +0100 |
|---|---|---|
| committer | Aaron Bohy <aab@odoo.com> | 2017-04-11 19:44:38 +0200 |
| commit | eaa66d11568b415b8d6b4c7a17a058b5ec1df4a6 (patch) | |
| tree | 822fe86e984e69f989c3574936fe1792ae96fe9b | |
| parent | e1eea38f2f940fbe252273a5a9d4d03474f81d0a (diff) | |
[REF] web: change internal representation of fields and view meta datas
* Data manager load_view returns a viewInfo object
* ViewInfo contains: the arch, fields and fieldsInfo objects, and some extra informations
* The fields come from the server and are frozen, without any view attributes (depend of the model).
* The fieldsInfo contains every view attributes and informations.
34 files changed, 468 insertions, 311 deletions
diff --git a/addons/base_import/static/src/js/base_import.js b/addons/base_import/static/src/js/base_import.js index 2c8f48d5c5d..971462cf12f 100644 --- a/addons/base_import/static/src/js/base_import.js +++ b/addons/base_import/static/src/js/base_import.js @@ -50,7 +50,7 @@ function jsonp(form, attributes, callback) { // Mixins that enable the 'Import' feature var ImportViewMixin = { - init: function (arch, fields, params) { + init: function (viewInfo, params) { var importEnabled = 'import_enabled' in params ? params.import_enabled : true; // if true, the 'Import' button will be visible this.controllerParams.importEnabled = importEnabled; diff --git a/addons/mail/static/src/js/basic_view.js b/addons/mail/static/src/js/basic_view.js index 624c66c0888..383d5e68f4e 100644 --- a/addons/mail/static/src/js/basic_view.js +++ b/addons/mail/static/src/js/basic_view.js @@ -6,28 +6,19 @@ var BasicView = require('web.BasicView'); var mailWidgets = ['mail_followers', 'mail_thread', 'mail_activity', 'kanban_activity']; BasicView.include({ - init: function () { - this.mailFields = {}; + init: function (viewInfo, params) { this._super.apply(this, arguments); + this.mailFields = {}; + for (var fieldName in viewInfo.fieldsInfo) { + var fieldInfo = viewInfo.fieldsInfo[fieldName]; + if (_.contains(mailWidgets, fieldInfo.widget)) { + this.mailFields[fieldInfo.widget] = fieldName; + fieldInfo.__no_fetch = true; + } + } this.rendererParams.activeActions = this.controllerParams.activeActions; this.rendererParams.mailFields = this.mailFields; }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * @param {Object} field - the field properties - * @param {Object} attrs - the field attributes (from the xml) - */ - _processField: function (field, attrs) { - if (_.contains(mailWidgets, attrs.widget)) { - this.mailFields[attrs.widget] = attrs.name; - field.__no_fetch = true; - } - this._super.apply(this, arguments); - }, }); }); diff --git a/addons/mail/static/src/js/chatter.js b/addons/mail/static/src/js/chatter.js index 7c27dad7006..41733fbbe89 100644 --- a/addons/mail/static/src/js/chatter.js +++ b/addons/mail/static/src/js/chatter.js @@ -56,7 +56,7 @@ var Chatter = Widget.extend(chat_mixin, { } if (mailFields.mail_thread) { this.fields.thread = new ThreadField(this, mailFields.mail_thread, record, options); - var nodeOptions = this.record.fieldAttrs[mailFields.mail_thread].options; + var nodeOptions = this.record.fieldsInfo[mailFields.mail_thread].options; this.hasLogButton = nodeOptions.display_log_button; } }, diff --git a/addons/web/static/src/js/chrome/view_manager.js b/addons/web/static/src/js/chrome/view_manager.js index af1d037bc2f..45be7a9484a 100644 --- a/addons/web/static/src/js/chrome/view_manager.js +++ b/addons/web/static/src/js/chrome/view_manager.js @@ -348,7 +348,7 @@ var ViewManager = Widget.extend(ControlPanelMixin, { params.mode = params.initial_mode || 'edit'; } - view = new View(arch, view.fields_view.fields, params); + view = new View(view.fields_view, params); return view.getController(this).then(function(controller) { controller.on('history_back', this, function() { if (self.action_manager) self.action_manager.trigger('history_back'); diff --git a/addons/web/static/src/js/core/utils.js b/addons/web/static/src/js/core/utils.js index 578eb3478f1..585be4d37b8 100644 --- a/addons/web/static/src/js/core/utils.js +++ b/addons/web/static/src/js/core/utils.js @@ -429,6 +429,33 @@ var utils = { }); return str; }, + /** + * Visit a tree of objects, where each children are in an attribute 'children'. + * For each children, we call the callback function given in arguments. + * + * @param {Object} tree an object describing a tree structure + * @param {function} f a callback + */ + traverse: function (tree, f) { + if (f(tree)) { + _.each(tree.children, function (c) { utils.traverse(c, f); }); + } + }, + /** + * Visit a tree of objects and freeze all + * + * @param {Object} obj + */ + deepFreeze: function (obj) { + var propNames = Object.getOwnPropertyNames(obj); + propNames.forEach(function(name) { + var prop = obj[name]; + if (typeof prop == 'object' && prop !== null) + utils.deepFreeze(prop); + }); + return Object.freeze(obj); + }, + }; return utils; diff --git a/addons/web/static/src/js/fields/abstract_field.js b/addons/web/static/src/js/fields/abstract_field.js index 2cfe961037c..25c530d8fd4 100644 --- a/addons/web/static/src/js/fields/abstract_field.js +++ b/addons/web/static/src/js/fields/abstract_field.js @@ -88,8 +88,9 @@ var AbstractField = Widget.extend({ // such as the type, the comodel (relation), ... this.field = record.fields[name]; - // the 'attrs' property contains the attributes of the xml 'field' tag - this.attrs = record.fieldAttrs[name] || {}; + // the 'attrs' property contains the attributes of the xml 'field' tag, + // the inner views... + this.attrs = record.fieldsInfo && record.fieldsInfo[name] || {}; // modifiers this.modifiers = JSON.parse(this.attrs.modifiers || "{}"); diff --git a/addons/web/static/src/js/fields/field_utils.js b/addons/web/static/src/js/fields/field_utils.js index bee1681a067..9c5afb71dd9 100644 --- a/addons/web/static/src/js/fields/field_utils.js +++ b/addons/web/static/src/js/fields/field_utils.js @@ -20,6 +20,8 @@ var session = require('web.session'); var time = require('web.time'); var utils = require('web.utils'); +var _t = core._t; + //------------------------------------------------------------------------------ // Formatting //------------------------------------------------------------------------------ @@ -191,10 +193,13 @@ function formatMany2one(value) { * @returns {string} */ function formatMany2Many(value) { - var names = _.map(value.data, function (p) { - return p.data.display_name; - }); - return names.join(', '); + if (value.data.length === 0) { + return value.data.length + _t('0 records'); + } else if (value.data.length === 1) { + return value.data.length + _t('1 record'); + } else { + return value.data.length + _t(' records'); + } } function formatMonetary(value, field, options) { diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index 408b4c5a3ea..923e8400b1e 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -520,7 +520,7 @@ var FieldX2Many = AbstractField.extend({ this._super.apply(this, arguments); this.operations = []; this.isReadonly = this.mode === 'readonly'; - this.view = this.field.views.tree || this.field.views.kanban; + this.view = this.attrs.views.list || this.attrs.views.kanban; this.activeActions = {}; var arch = this.view && this.view.arch; if (arch) { @@ -704,7 +704,7 @@ var FieldX2Many = AbstractField.extend({ _.extend(event.data, { context: this.record.getContext({fieldName: this.name}), domain: this.record.getDomain({fieldName: this.name}), - form_view: this.field.views && this.field.views.form, + form_view: this.attrs.views && this.attrs.views.form, readonly: this.mode === 'readonly', string: this.string, }); @@ -799,6 +799,7 @@ var FieldOne2Many = FieldX2Many.extend({ domain: this.record.getDomain({fieldName: this.name}), context: this.record.getContext({fieldName: this.name}), field: this.field, + attrs: this.attrs, on_save: function (record) { this._setValue({ operation: 'ADD', @@ -867,7 +868,7 @@ var FieldMany2Many = FieldX2Many.extend({ context: this.record.getContext({fieldName: this.name}), title: _t("Add: ") + this.string, no_create: this.nodeOptions.no_create || !this.activeActions.create, - fields_view: this.field.views.form, + fields_view: this.attrs.views.form, on_selected: function (res_ids) { var new_ids = _.difference(res_ids, self.value.res_ids); if (new_ids.length) { @@ -911,6 +912,8 @@ var FieldMany2ManyTags = AbstractField.extend({ 'keydown .o_form_field_many2one input': '_onKeyDown', }), + fetchSubFields: true, + //-------------------------------------------------------------------------- // Public //-------------------------------------------------------------------------- diff --git a/addons/web/static/src/js/services/data_manager.js b/addons/web/static/src/js/services/data_manager.js index e86f1b9bb39..3fdb0986624 100644 --- a/addons/web/static/src/js/services/data_manager.js +++ b/addons/web/static/src/js/services/data_manager.js @@ -1,7 +1,10 @@ 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 session = require('web.session'); var utils = require('web.utils'); @@ -112,7 +115,13 @@ return core.Class.extend({ } return this._cache.views[key].then(function (views) { - return $.extend(true, {}, views); + return _.mapObject(views, function (view, viewType) { + return _.extend(view, self._processFieldsView({ + type: viewType, + arch: view.arch, + fields: view.fields, + })); + }); }); }, @@ -224,6 +233,149 @@ 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"; + } + + if (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) { + 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'; + } + } 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 (attrs.Widget.prototype.fetchSubFields) { + attrs.relatedFields = { + display_name: {type: 'char'}, + //id: {type: 'integer'}, + }; + attrs.fieldsInfo = {display_name: {}, id: {}}; + if (attrs.color || 'color') { + attrs.relatedFields[attrs.color || 'color'] = {type: 'int'}; + attrs.fieldsInfo.color = {}; + } + } + } + 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 (node.tag === 'field') { + fieldsInfo[node.attrs.name] = self._processField(viewType, + fields[node.attrs.name], node.attrs ? _.clone(node.attrs) : {}); + 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) { + viewInfo.fieldsInfo = this._processFields(viewInfo.type, viewInfo.arch, viewInfo.fields); + // by default display fetch display_name and id + if (!viewInfo.fields.display_name) { + viewInfo.fields.display_name = {type: 'char'}; + viewInfo.fieldsInfo.display_name = {}; + } + 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); + } + } + 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 98c6aaac22e..e1066e56e92 100644 --- a/addons/web/static/src/js/views/abstract_view.js +++ b/addons/web/static/src/js/views/abstract_view.js @@ -60,8 +60,10 @@ var AbstractView = Class.extend({ * * @constructs AbstractView * - * @param {any} arch - * @param {any} fields + * @param {Object} viewInfo + * @param {Object} viewInfo.arch + * @param {Object} viewInfo.fields + * @param {Object} viewInfo.fieldsInfo * @param {Object} params * @param {string} params.modelName The actual model name * @param {Object} params.context @@ -72,17 +74,17 @@ var AbstractView = Class.extend({ * @param {number[]} [params.ids] * @param {string} [params.action.help] */ - init: function (arch, fields, params) { + init: function (viewInfo, params) { this.rendererParams = { - arch: arch + arch: viewInfo.arch, }; this.controllerParams = { modelName: params.modelName, activeActions: { - edit: arch.attrs.edit ? JSON.parse(arch.attrs.edit) : true, - create: arch.attrs.create ? JSON.parse(arch.attrs.create) : true, - delete: arch.attrs.delete ? JSON.parse(arch.attrs.delete) : true, + 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, }, noContentHelp: params.action && params.action.help, }; 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 d5f3c86cc8e..023571d19a4 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -25,7 +25,7 @@ odoo.define('web.BasicModel', function (require) { * domain: {*[]}, * fieldNames: {string[]}, * fields: {Object}, - * fieldAttrs: {Object}, + * fieldsInfo: {Object}, * getContext: {function}, * getDomain: {function}, * groupedBy: {string[]}, @@ -160,7 +160,7 @@ var BasicModel = AbstractModel.extend({ var position = (options && options.position) || 'top'; var params = { context: context, - fieldAttrs: list.fieldAttrs, + fieldsInfo: list.fieldsInfo, fieldNames: list.fieldNames, fields: list.fields, parentID: list.id, @@ -238,7 +238,7 @@ var BasicModel = AbstractModel.extend({ var index = record.res_ids.indexOf(record.res_id); record.res_ids.splice(index + 1, 0, res_id); return self.load({ - fieldAttrs: record.fieldAttrs, + fieldsInfo: record.fieldsInfo, fieldNames: record.fieldNames, fields: record.fields, modelName: record.model, @@ -395,7 +395,7 @@ var BasicModel = AbstractModel.extend({ * @todo document all params * * @param {any} params - * @param {Object} [params.fieldAttrs={}] contains the attrs of each field + * @param {Object} [params.fieldsInfo={}] contains the fieldInfo of each field * @param {Array} [params.fieldNames] the name of fields to load, the list * of all fields by default * @param {Object} params.fields contains the description of each field @@ -428,7 +428,7 @@ var BasicModel = AbstractModel.extend({ * @param {any} params * @param {string} modelName model name * @param {Object} params.context the context for the new record - * @param {Object} [params.fieldAttrs={}] contains the attrs of each field + * @param {Object} [params.fieldsInfo={}] contains the fieldInfo of each field * @param {Array} params.fieldNames the name of fields to load, the list * of all fields by default * @param {Object} params.fields contains the description of each field @@ -463,7 +463,7 @@ var BasicModel = AbstractModel.extend({ data: data, fields: params.fields, fieldNames: params.fieldNames, - fieldAttrs: params.fieldAttrs, + fieldsInfo: params.fieldsInfo, context: params.context, parentID: params.parentID, relationField: params.relationField, @@ -483,15 +483,17 @@ var BasicModel = AbstractModel.extend({ }); record._changes[name] = rec.id; } else if (field.type === 'one2many' || field.type === 'many2many') { - var attrs = record.fieldAttrs[name]; + var fieldInfo = record.fieldsInfo[name]; + var view = fieldInfo.views && fieldInfo.views[fieldInfo.mode]; + var x2manyList = self._makeDataPoint({ context: record.context, - fieldAttrs: field.fieldAttrs, - fields: field.relatedFields, - limit: field.limit, + fieldsInfo: fieldInfo.fieldsInfo || view && view.fieldsInfo, + fields: fieldInfo.relatedFields || view && view.fields, + limit: fieldInfo.limit, modelName: field.relation, parentID: record.id, - rawContext: attrs && attrs.context, + rawContext: fieldInfo && fieldInfo.context, relationField: field.relation_field, res_ids: [], static: true, @@ -508,8 +510,8 @@ var BasicModel = AbstractModel.extend({ r = self._makeDataPoint({ modelName: x2manyList.model, context: x2manyList.context, - fields: field.relatedFields, - fieldAttrs: field.fieldAttrs, + fieldsInfo: fieldInfo.fieldsInfo || view && view.fieldsInfo, + fields: fieldInfo.relatedFields || view && view.fields, }); x2manyList._changes.push(r.id); r._changes = value[2]; @@ -540,8 +542,8 @@ var BasicModel = AbstractModel.extend({ r = self._makeDataPoint({ modelName: x2manyList.model, context: x2manyList.context, - fields: field.relatedFields, - fieldAttrs: field.fieldAttrs, + fieldsInfo: fieldInfo.fieldsInfo || view && view.fieldsInfo, + fields: fieldInfo.relatedFields || view && view.fields, res_id: value, }); if (!x2manyList._changes) { @@ -599,10 +601,10 @@ var BasicModel = AbstractModel.extend({ * * @param {string} model name of the model * @param {Object[]} fields a description of field properties - * @param {Object} [attrs] various field attrs that we want to set + * @param {Object} [fieldInfo] various field fieldInfo that we want to set * @returns {string} the local id for the created resource */ - makeRecord: function (model, fields, attrs) { + makeRecord: function (model, fields, fieldInfo) { var self = this; var record_fields = {}; _.each(fields, function (field) { @@ -611,7 +613,7 @@ var BasicModel = AbstractModel.extend({ var record = this._makeDataPoint({ modelName: model, fields: record_fields, - fieldAttrs: attrs, + fieldsInfo: fieldInfo, }); _.each(fields, function (field) { if ('value' in field) { @@ -704,7 +706,7 @@ var BasicModel = AbstractModel.extend({ if ('currentId' in options && !options.currentId) { var params = { context: element.context, - fieldAttrs: element.fieldAttrs, + fieldsInfo: element.fieldsInfo, fieldNames: element.fieldNames, fields: element.fields, }; @@ -836,7 +838,7 @@ var BasicModel = AbstractModel.extend({ /** * Set fieldNames. * Set fields on a record element (current fields as defaults) - * Set fieldattrs. + * Set fieldsInfo. * It is mostly useful for the cases where a record element is shared * between various views, such as a one2many with a tree and a form view. * @@ -844,22 +846,23 @@ var BasicModel = AbstractModel.extend({ * @param {Object} props * @param {Object} props.fieldNames * @param {Object} props.fields - * @param {Object} props.fieldAttrs + * @param {Object} props.fieldsInfo * @return {Object} * @return {Object} result.fieldNames the previous field names * @return {Object} result.fields the previous description fields - * @return {Object} result.fieldattrs the previous field attrs + * @return {Object} result.fieldsInfo the previous field fieldInfo */ setFieldProps: function (recordID, props) { var record = this.localData[recordID]; var result = { fieldNames: record.fieldNames, fields: record.fields, - fieldAttrs: record.fieldAttrs, + fieldsInfo: record.fieldsInfo, }; - record.fieldNames = props.fieldNames; + record.mode = props.mode; + record.fieldNames = Object.keys(props.fieldsInfo); record.fields = _.defaults(props.fields, record.fields); - record.fieldAttrs = props.fieldAttrs; + record.fieldsInfo = props.fieldsInfo; return result; }, /** @@ -979,6 +982,7 @@ var BasicModel = AbstractModel.extend({ context: record.context, data: rel_data, fields: {}, + fieldsInfo: {}, modelName: record.fields[fieldName].relation, }); record._changes[fieldName] = rec.id; @@ -1063,7 +1067,7 @@ var BasicModel = AbstractModel.extend({ var params = { context: context, fields: list.fields, - fieldAttrs: list.fieldAttrs, + fieldsInfo: list.fieldsInfo, modelName: list.model, fieldNames: _.keys(command[2]), }; @@ -1128,6 +1132,8 @@ var BasicModel = AbstractModel.extend({ var self = this; var list = this.localData[record._changes[fieldName] || record.data[fieldName]]; var field = record.fields[fieldName]; + var fieldInfo = record.fieldsInfo[fieldName]; + var view = fieldInfo.views && fieldInfo.views[fieldInfo.mode]; var rec; var defs = []; list._changes = list._changes || list.data; @@ -1158,8 +1164,8 @@ var BasicModel = AbstractModel.extend({ rec = self._makeDataPoint({ context: record.context, modelName: field.relation, - fields: field.relatedFields, - fieldAttrs: field.fieldAttrs, + fields: fieldInfo.relatedFields || view && view.fields, + fieldsInfo: fieldInfo.fieldsInfo || view && view.fieldsInfo, res_id: d.id, }); list_records[d.id] = rec; @@ -1237,9 +1243,10 @@ var BasicModel = AbstractModel.extend({ var specs = {}; _.each(record.fieldNames, function (name) { var field = record.fields[name]; + var fieldInfo = record.fieldsInfo[name]; specs[name] = (field.onChange) || ""; - _.each(field.views, function (view) { - _.each(view.fieldAttrs, function (field, subname) { + _.each(fieldInfo.views, function (view) { + _.each(view.fieldsInfo, function (field, subname) { specs[name + '.' + subname] = (view.fields[subname].onChange) || ""; }); }); @@ -1374,8 +1381,8 @@ var BasicModel = AbstractModel.extend({ var self = this; var fieldNames = []; return $.when.apply($, _.map(record.fieldNames, function (name) { - var attrs = record.fieldAttrs[name] || {}; - var Widget = attrs.Widget; + var fieldInfo = record.fieldsInfo[name] || {}; + var Widget = fieldInfo.Widget; if (Widget && Widget.prototype.specialData) { return self[Widget.prototype.specialData](record, name).then(function (data) { if (data === undefined) { @@ -1501,7 +1508,7 @@ var BasicModel = AbstractModel.extend({ * the undefined value. */ _fetchSpecialStatus: function (record, fieldName) { - var foldField = record.fieldAttrs[fieldName].options.fold_field; + var foldField = record.fieldsInfo[fieldName].options.fold_field; var fieldsToRead = foldField ? [foldField] : []; return this._fetchSpecialMany2ones(record, fieldName, fieldsToRead).then(function (m2os) { _.each(m2os, function (m2o) { @@ -1525,7 +1532,7 @@ var BasicModel = AbstractModel.extend({ _fetchSpecialDomain: function (record, fieldName) { var context = record.getContext({fieldName: fieldName}); - var domainModel = record.fieldAttrs[fieldName].options.model; + var domainModel = record.fieldsInfo[fieldName].options.model; if (record.data.hasOwnProperty(domainModel)) { domainModel = record._changes && record._changes[domainModel] || record.data[domainModel]; } @@ -1600,14 +1607,15 @@ var BasicModel = AbstractModel.extend({ _.each(fieldNames, function (fieldName) { var field = record.fields[fieldName]; if (field.type === 'one2many' || field.type === 'many2many') { - var attrs = record.fieldAttrs[fieldName]; - var rawContext = attrs && attrs.context; + var fieldInfo = record.fieldsInfo[fieldName]; + var rawContext = fieldInfo && fieldInfo.context; + var view = fieldInfo.views && fieldInfo.views[fieldInfo.mode]; var ids = record.data[fieldName] || []; var list = self._makeDataPoint({ count: ids.length, - fieldAttrs: field.fieldAttrs, - fields: field.relatedFields, - limit: field.limit, + fieldsInfo: fieldInfo.fieldsInfo || view && view.fieldsInfo, + fields: fieldInfo.relatedFields || view && view.fields, + limit: fieldInfo.limit, modelName: field.relation, res_ids: ids, static: true, @@ -1617,7 +1625,7 @@ var BasicModel = AbstractModel.extend({ relationField: field.relation_field, }); record.data[fieldName] = list.id; - if (!field.__no_fetch) { + if (!fieldInfo.__no_fetch) { var def = self._readUngroupedList(list).then(function () { return self._fetchX2ManysBatched(list); }); @@ -1638,6 +1646,8 @@ var BasicModel = AbstractModel.extend({ _fetchX2ManyBatched: function (list, fieldName) { var self = this; var field = list.fields[fieldName]; + var fieldInfo = list.fieldsInfo[fieldName]; + var view = fieldInfo.views && fieldInfo.views[fieldInfo.mode]; // step 1: collect ids var ids = []; @@ -1645,8 +1655,8 @@ var BasicModel = AbstractModel.extend({ var record = self.localData[dataPoint]; ids = _.unique(ids.concat(record.data[fieldName] || [])); var m2mList = self._makeDataPoint({ - fieldAttrs: field.fieldAttrs, - fields: field.relatedFields, + fieldsInfo: fieldInfo.fieldsInfo || view && view.fieldsInfo, + fields: fieldInfo.relatedFields || view && view.fields, modelName: field.relation, res_ids: record.data[fieldName], static: true, @@ -1655,13 +1665,13 @@ var BasicModel = AbstractModel.extend({ record.data[fieldName] = m2mList.id; }); - if (!ids.length || field.__no_fetch) { + if (!ids.length || fieldInfo.__no_fetch) { return $.when(); } // step 2: fetch data from server return this._rpc(field.relation, 'read') - .args([ids, _.keys(field.relatedFields)]) + .args([ids, _.keys(fieldInfo.relatedFields)]) .withContext({}) // FIXME .exec() .then(function (results) { @@ -1670,8 +1680,8 @@ var BasicModel = AbstractModel.extend({ return self._makeDataPoint({ modelName: field.relation, data: result, - fields: field.relatedFields, - fieldAttrs: field.fieldAttrs, + fieldsInfo: fieldInfo.fieldsInfo || view && view.fieldsInfo, + fields: fieldInfo.relatedFields || view && view.fields, }); }); @@ -1796,9 +1806,9 @@ var BasicModel = AbstractModel.extend({ 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); + var fieldInfo = element.fieldsInfo[options.fieldName]; + if (fieldInfo && fieldInfo.context) { + context.add(fieldInfo.context); } else { var fieldParams = element.fields[options.fieldName]; if (fieldParams.context) { @@ -1828,10 +1838,10 @@ var BasicModel = AbstractModel.extend({ */ _getDomain: function (element, options) { if (options && options.fieldName) { - var attrs = element.fieldAttrs[options.fieldName]; - if (attrs && attrs.domain) { + var fieldInfo = element.fieldsInfo[options.fieldName]; + if (fieldInfo && fieldInfo.domain) { return Domain.prototype.stringToArray( - attrs.domain, + fieldInfo.domain, this._getEvalContext(element) ); } @@ -1899,7 +1909,7 @@ var BasicModel = AbstractModel.extend({ * the resource in the localData object. * * @param {Object} params - * @param {Object} [params.fieldAttrs={}] contains the attrs of each field + * @param {Object} [params.fieldsInfo={}] contains the fieldInfo of each field * @param {Array} [params.fieldNames] the name of fields to load, the list * of all fields by default * @param {Object} params.fields contains the description of each field @@ -1907,7 +1917,6 @@ var BasicModel = AbstractModel.extend({ */ _makeDataPoint: function (params) { var type = params.type || ('domain' in params && 'list') || 'record'; - var res_id, value; var res_ids = params.res_ids || []; if (type === 'record') { @@ -1923,6 +1932,13 @@ var BasicModel = AbstractModel.extend({ id: {type: 'integer'}, }, params.fields); + if (!params.fieldsInfo) { + params.fieldsInfo = {}; + for (var fieldName in fields) { + params.fieldsInfo[fieldName] = {}; + } + } + var dataPoint = { _cache: type === 'list' ? {} : undefined, _changes: null, @@ -1931,9 +1947,9 @@ var BasicModel = AbstractModel.extend({ count: params.count || res_ids.length, data: params.data || (type === 'record' ? {} : []), domain: params.domain || [], - fieldNames: params.fieldNames || Object.keys(fields), + fieldNames: params.fieldNames || Object.keys(params.fieldsInfo), fields: fields, - fieldAttrs: params.fieldAttrs || {}, + fieldsInfo: params.fieldsInfo || {}, groupedBy: params.groupedBy || [], id: _.uniqueId(params.modelName + '_'), isOpen: params.isOpen, @@ -1979,6 +1995,10 @@ var BasicModel = AbstractModel.extend({ // the many2one value is of the form [id, display_name] var r = self._makeDataPoint({ modelName: field.relation, + fields: { + display_name: {type: 'char'}, + id: {type: 'integer'}, + }, data: { display_name: val[1], id: val[0], @@ -2013,8 +2033,8 @@ var BasicModel = AbstractModel.extend({ var defs = []; _.each(record.fieldNames, function (name) { var field = record.fields[name]; - var attrs = record.fieldAttrs[name] || {}; - var options = attrs.options || {}; + var fieldInfo = record.fieldsInfo[name] || {}; + var options = fieldInfo.options || {}; if (options.always_reload) { if (record.fields[name].type === 'many2one' && record.data[name]) { var element = self.localData[record.data[name]]; @@ -2076,7 +2096,7 @@ var BasicModel = AbstractModel.extend({ context: list.context, fields: list.fields, fieldNames: list.fieldNames, - fieldAttrs: list.fieldAttrs, + fieldsInfo: list.fieldsInfo, value: group[rawGroupBy], aggregateValues: aggregateValues, groupedBy: list.groupedBy.slice(1), @@ -2152,7 +2172,7 @@ var BasicModel = AbstractModel.extend({ } else { dataPoint = self._makeDataPoint({ data: _.findWhere(records, {id: id}), - fieldAttrs: list.fieldAttrs, + fieldsInfo: list.fieldsInfo, fieldNames: list.fieldNames, fields: list.fields, modelName: list.model, @@ -2211,7 +2231,7 @@ var BasicModel = AbstractModel.extend({ data: record, fields: list.fields, fieldNames: list.fieldNames, - fieldAttrs: list.fieldAttrs, + fieldsInfo: list.fieldsInfo, modelName: list.model, parentID: list.id, }); 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 35c0dd5b6f0..5b8a3a7d0c9 100644 --- a/addons/web/static/src/js/views/basic/basic_view.js +++ b/addons/web/static/src/js/views/basic/basic_view.js @@ -13,23 +13,6 @@ odoo.define('web.BasicView', function (require) { var AbstractView = require('web.AbstractView'); var BasicController = require('web.BasicController'); var BasicModel = require('web.BasicModel'); -var fieldRegistry = require('web.field_registry'); -var pyeval = require('web.pyeval'); - -/** - * Visit a tree of objects, where each children are in an attribute 'children'. - * For each children, we call the callback function given in arguments. - * - * @todo move this somewhere in web.utils - * - * @param {Object} tree an object describing a tree structure - * @param {function} f a callback - */ -function traverse(tree, f) { - if (f(tree)) { - _.each(tree.children, function (c) { traverse(c, f); }); - } -} var BasicView = AbstractView.extend({ config: _.extend({}, AbstractView.prototype.config, { @@ -45,51 +28,21 @@ var BasicView = AbstractView.extend({ * augmented with their attrs and some flags if they require a * particular handling. * - * @param {Object} arch - * @param {Object} fields + * @param {Object} viewInfo * @param {Object} params */ - init: function (arch, fields, params) { + init: function (viewInfo, params) { this._super.apply(this, arguments); - var result = this._processFieldsView(arch, fields); - var fieldAttrs = result.fieldAttrs; - var viewFields = result.view_fields; - var fieldNames = _.keys(viewFields); - fields = _.defaults(viewFields, fields); - var self = this; - // 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 - for (var fieldName in fields) { - var field = fields[fieldName]; - _.each(field.views, function (innerFieldsView) { - result = self._processFieldsView(innerFieldsView.arch, innerFieldsView.fields); - innerFieldsView.fieldAttrs = result.fieldAttrs; - innerFieldsView.fields = result.view_fields; - }); - if (field.type === 'one2many' || field.type === 'many2many') { - field.relatedFields = { - color: {type: 'int'}, - display_name: {type: 'char'}, - id: {type: 'integer'}, - }; - field.fieldAttrs = {}; - } - } - this.controllerParams.confirmOnDelete = true; - this.controllerParams.archiveEnabled = 'active' in fields; + this.controllerParams.archiveEnabled = 'active' in viewInfo.fields; this.controllerParams.hasButtons = 'action_buttons' in params ? params.action_buttons : true; - this.loadParams.fieldAttrs = fieldAttrs; - this.loadParams.fieldNames = fieldNames; - this.loadParams.fields = fields; - this.loadParams.limit = parseInt(arch.attrs.limit, 10) || params.limit; + this.loadParams.fieldsInfo = viewInfo.fieldsInfo; + this.loadParams.fieldNames = _.keys(viewInfo.fieldsInfo); + this.loadParams.fields = viewInfo.fields; + this.loadParams.limit = parseInt(viewInfo.arch.attrs.limit, 10) || params.limit; this.recordID = params.recordID; }, @@ -116,76 +69,6 @@ var BasicView = AbstractView.extend({ } return this._super.apply(this, arguments); }, - /** - * Visit all nodes in the arch field and process each fields - * - * @param {Object} arch - * @param {Object} fields - * @returns {Object} An object with 2 keys: view_fields and fieldAttrs - */ - _processFieldsView: function (arch, fields) { - var self = this; - var viewFields = Object.create(null); - var fieldAttrs = Object.create(null); - traverse(arch, function (node) { - if (typeof node === 'string') { - return false; - } - if (node.tag === 'field') { - var attrs = node.attrs ? _.clone(node.attrs) : {}; - var field = _.clone(fields[node.attrs.name]); - self._processField(field, attrs); - viewFields[node.attrs.name] = field; - fieldAttrs[node.attrs.name] = attrs; - return false; - } - return node.tag !== 'arch'; - }); - return { - view_fields: viewFields, - fieldAttrs: fieldAttrs, - }; - }, - /** - * Process a field node, in particular, put a flag on the field to give - * special directives to the BasicModel. - * - * @param {Object} field - the field properties - * @param {Object} attrs - the field attributes (from the xml) - */ - _processField: function (field, attrs) { - attrs.Widget = this._getFieldWidgetClass(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 = "1"; - } - }, - - /** - * 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 {Object} field - * @param {Object} attrs - * @returns {function|null} AbstractField specialization Class - */ - _getFieldWidgetClass: function (field, attrs) { - var viewName = this.rendererParams.arch.tag; - - var Widget; - if (attrs.widget) { - Widget = fieldRegistry.getAny([viewName + "." + attrs.widget, attrs.widget]); - if (!Widget) { - console.warn("Missing widget: ", attrs.widget, " for field", attrs.name, "of type", field.type); - } - } - return Widget || fieldRegistry.getAny([viewName + "." + field.type, field.type, "abstract"]); - }, }); return BasicView; diff --git a/addons/web/static/src/js/views/calendar/calendar_model.js b/addons/web/static/src/js/views/calendar/calendar_model.js index 6617854e9ad..07327616097 100644 --- a/addons/web/static/src/js/views/calendar/calendar_model.js +++ b/addons/web/static/src/js/views/calendar/calendar_model.js @@ -151,6 +151,7 @@ return AbstractModel.extend({ this.modelName = params.modelName; this.fields = params.fields; this.fieldNames = params.fieldNames; + this.fieldsInfo = params.fieldsInfo; this.mapping = params.mapping; this.mode = params.mode; // one of month, week or day this.scales = params.scales; // one of month, week or day 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 f72dc8e640e..3bd8099484b 100644 --- a/addons/web/static/src/js/views/calendar/calendar_view.js +++ b/addons/web/static/src/js/views/calendar/calendar_view.js @@ -26,8 +26,10 @@ var CalendarView = AbstractView.extend({ Controller: CalendarController, Renderer: CalendarRenderer, }, - init: function (arch, fields, params) { + init: function (viewInfo, params) { this._super.apply(this, arguments); + var arch = viewInfo.arch; + var fields = viewInfo.fields; var attrs = arch.attrs; if (!attrs.date_start) { @@ -121,6 +123,7 @@ var CalendarView = AbstractView.extend({ this.loadParams.fieldNames = _.uniq(fieldNames); this.loadParams.mapping = mapping; this.loadParams.fields = fields; + this.loadParams.fieldsInfo = viewInfo.fieldsInfo; this.loadParams.editable = !this.controllerParams.read_only_mode && !fields[mapping.date_start].readonly; this.loadParams.eventLimit = eventLimit; this.loadParams.field_color = attrs.color; 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 341d23e0bed..940d27d386c 100644 --- a/addons/web/static/src/js/views/form/form_controller.js +++ b/addons/web/static/src/js/views/form/form_controller.js @@ -66,7 +66,7 @@ var FormController = BasicController.extend({ var record = this.model.get(this.handle, {raw: true}); return this.model.makeDefaultRecord(this.modelName, { context: this.context, - fieldAttrs: record.fieldAttrs, + fieldsInfo: record.fieldsInfo, fieldNames: record.fieldNames, fields: record.fields, res_ids: record.res_ids, @@ -408,6 +408,7 @@ var FormController = BasicController.extend({ */ _onAddOne2ManyRecord: function (event) { var field = event.data.field; + var attrs = event.data.attrs; new dialogs.FormViewDialog(this, { res_model: field.relation, shouldSaveLocally: true, @@ -416,7 +417,7 @@ var FormController = BasicController.extend({ on_save: event.data.on_save, title: _t('Create: ') + field.string, initial_view: 'form', - fields_view: field.views ? field.views.form : undefined, + fields_view: attrs.views ? attrs.views.form : undefined, form_view_options: {'not_interactible_on_create': true}, model: this.model, parentID: this.handle, @@ -520,7 +521,7 @@ var FormController = BasicController.extend({ var res_id = record.res_id; var fieldsViewDef; if (event.data.form_view) { - fieldsViewDef = $.when(event.data.form_view); + fieldsViewDef = $.when({form: event.data.form_view}); } else { fieldsViewDef = this.loadViews(model, new Context(event.data.context), [[null, 'form']], {}); } diff --git a/addons/web/static/src/js/views/form/form_renderer.js b/addons/web/static/src/js/views/form/form_renderer.js index c5f4ba9044a..e477daf930a 100644 --- a/addons/web/static/src/js/views/form/form_renderer.js +++ b/addons/web/static/src/js/views/form/form_renderer.js @@ -268,7 +268,8 @@ return BasicRenderer.extend({ required: is_required, idForLabel: this._getIDForLabel(name), }; - var widget = new this.state.fieldAttrs[name].Widget(this, name, this.state, options); + var attrs = this.state.fieldsInfo[name]; + var widget = new attrs.Widget(this, name, this.state, options); this.widgets.push(widget); var def = widget.__widgetRenderAndInsert(function () {}); if (def.state() === 'pending') { 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 6578c7ada15..32f78136e50 100644 --- a/addons/web/static/src/js/views/form/form_view.js +++ b/addons/web/static/src/js/views/form/form_view.js @@ -5,7 +5,6 @@ var BasicView = require('web.BasicView'); var FormRenderer = require('web.FormRenderer'); var FormController = require('web.FormController'); var Context = require('web.Context'); -var config = require('web.config'); var core = require('web.core'); var _lt = core._lt; @@ -23,7 +22,7 @@ var FormView = BasicView.extend({ /** * @override */ - init: function (arch, fields, params) { + init: function (viewInfo, params) { this._super.apply(this, arguments); var mode = params.mode || (params.currentId ? 'readonly' : 'edit'); @@ -51,45 +50,23 @@ var FormView = BasicView.extend({ var defs = []; var fields = this.loadParams.fields; - _.each(self.loadParams.fieldAttrs, function (attrs, fieldName) { + _.each(self.loadParams.fieldsInfo, function (attrs, fieldName) { var field = fields[fieldName]; if (field.type !== 'one2many' && field.type !== 'many2many') { return; } - if (attrs.Widget.prototype.useSubview && !(attrs.invisible && JSON.parse(attrs.invisible))) { - var mode = attrs.mode; - if (!mode) { - if (field.views.tree && field.views.kanban) { - mode = 'tree'; - } else if (!field.views.tree && field.views.kanban) { - mode = 'kanban'; - } else { - mode = 'tree,kanban'; - } - } - if (mode.indexOf(',') !== -1) { - mode = config.device.size_class !== config.device.SIZES.XS ? 'tree' : 'kanban'; - } - if (field.views[mode]) { - field.relatedFields = field.views[mode].fields; - field.fieldAttrs = field.views[mode].fieldAttrs; - field.limit = mode === "tree" ? 80 : 40; - return; - } + attrs.limit = attrs.mode === "tree" ? 80 : 40; + + if (attrs.Widget.prototype.useSubview && !(attrs.invisible && JSON.parse(attrs.invisible)) && !attrs.views[attrs.mode]) { defs.push(parent.loadViews( field.relation, new Context(self.loadParams.context), - [[null, mode === 'tree' ? 'list' : mode]], + [[null, attrs.mode === 'tree' ? 'list' : attrs.mode]], {}) .then(function (views) { for (var viewName in views) { - var view = views[viewName]; - var fieldView = self._processFieldsView(view.arch, view.fields); - field.views[viewName === 'list' ? 'tree' : viewName] = view; - field.relatedFields = fieldView.view_fields; - field.fieldAttrs = fieldView.fieldAttrs; - view.fieldAttrs = fieldView.fieldAttrs; + attrs.views[viewName] = views[viewName]; } })); } 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 3422a0922d3..1d4ee29ef63 100644 --- a/addons/web/static/src/js/views/graph/graph_view.js +++ b/addons/web/static/src/js/views/graph/graph_view.js @@ -27,13 +27,13 @@ var GraphView = AbstractView.extend({ /** * @override */ - init: function (arch, fields) { + init: function (viewInfo) { this._super.apply(this, arguments); var initialMeasure = '__count__'; var initialGroupBys = []; - fields.__count__ = {string: _t("Count"), type: "integer"}; - arch.children.forEach(function (field) { + viewInfo.fields = _.defaults({__count__: {string: _t("Count"), type: "integer"}}, viewInfo.fields); + viewInfo.arch.children.forEach(function (field) { var name = field.attrs.name; if (field.attrs.interval) { name += ':' + field.attrs.interval; @@ -46,7 +46,7 @@ var GraphView = AbstractView.extend({ }); var measures = {__count__: {string: _t("Count"), type: "integer"}}; - _.each(fields, function (field, name) { + _.each(viewInfo.fields, function (field, name) { if (name !== 'id' && field.store === true) { if (field.type === 'integer' || field.type === 'float' || field.type === 'monetary') { measures[name] = field; @@ -55,12 +55,12 @@ var GraphView = AbstractView.extend({ }); this.controllerParams.measures = measures; - this.rendererParams.stacked = arch.attrs.stacked !== "False"; + this.rendererParams.stacked = viewInfo.arch.attrs.stacked !== "False"; - this.loadParams.initialMode = arch.attrs.type || 'bar'; + this.loadParams.initialMode = viewInfo.arch.attrs.type || 'bar'; this.loadParams.initialMeasure = initialMeasure; this.loadParams.initialGroupBys = initialGroupBys; - this.loadParams.fields = fields; + this.loadParams.fields = viewInfo.fields; }, }); diff --git a/addons/web/static/src/js/views/kanban/kanban_model.js b/addons/web/static/src/js/views/kanban/kanban_model.js index c600ab48b1e..454349a88c5 100644 --- a/addons/web/static/src/js/views/kanban/kanban_model.js +++ b/addons/web/static/src/js/views/kanban/kanban_model.js @@ -28,7 +28,7 @@ var KanbanModel = BasicModel.extend({ modelName: group.model, fields: group.fields, fieldNames: group.fieldNames, - fieldAttrs: group.fieldAttrs, + fieldsInfo: group.fieldsInfo, }); group.data.unshift(new_record); group.count++; diff --git a/addons/web/static/src/js/views/kanban/kanban_record.js b/addons/web/static/src/js/views/kanban/kanban_record.js index 542b25cdd0a..4217e4d25bb 100644 --- a/addons/web/static/src/js/views/kanban/kanban_record.js +++ b/addons/web/static/src/js/views/kanban/kanban_record.js @@ -27,7 +27,7 @@ var KanbanRecord = Widget.extend({ this._super(parent); this.fields = state.fields; - this.fieldAttrs = state.fieldAttrs; + this.fieldsInfo = state.fieldsInfo; this.modelName = state.model; this.options = options; @@ -83,7 +83,7 @@ var KanbanRecord = Widget.extend({ var widget = self.subWidgets[field_name]; if (!widget) { // the widget doesn't exist yet, so instanciate it - var Widget = self.fieldAttrs[field_name].Widget; + var Widget = self.fieldsInfo[field_name].Widget; if (Widget) { // some field's attrs might be record dependent (they start with // 't-att-') and should thus be evaluated, which is done by qweb @@ -93,14 +93,14 @@ var KanbanRecord = Widget.extend({ // that dict being shared between records, we don't modify it // in place var attrs = Object.create(null); - _.each(self.fieldAttrs[field_name], function (value, key) { + _.each(self.fieldsInfo[field_name], function (value, key) { if (_.str.startsWith(key, 't-att-')) { key = key.slice(6); value = $field.attr(key); } attrs[key] = value; }); - self.fieldAttrs[field_name] = attrs; + self.fieldsInfo[field_name] = attrs; widget = new Widget(self, field_name, self.state, self.options); self.subWidgets[field_name] = widget; @@ -276,14 +276,14 @@ var KanbanRecord = Widget.extend({ */ _setFieldDisplay: function (widget, fieldName) { // attribute display - if (this.fieldAttrs[fieldName].display === 'right') { + if (this.fieldsInfo[fieldName].display === 'right') { widget.$el.addClass('pull-right'); - } else if (this.fieldAttrs[fieldName].display === 'full') { + } else if (this.fieldsInfo[fieldName].display === 'full') { widget.$el.addClass('o_text_block'); } // attribute bold - if (this.fieldAttrs[fieldName].bold) { + if (this.fieldsInfo[fieldName].bold) { widget.$el.addClass('o_text_bold'); } }, 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 73949004952..9c8c03dfb27 100644 --- a/addons/web/static/src/js/views/kanban/kanban_view.js +++ b/addons/web/static/src/js/views/kanban/kanban_view.js @@ -20,9 +20,10 @@ var KanbanView = BasicView.extend({ Controller: KanbanController, Renderer: KanbanRenderer, }, - init: function (arch, fields, params) { + init: function (viewInfo, params) { this._super.apply(this, arguments); + var arch = viewInfo.arch; var activeActions = this.controllerParams.activeActions; activeActions = _.extend(activeActions, { group_edit: arch.attrs.group_edit ? JSON.parse(arch.attrs.group_edit) : true, diff --git a/addons/web/static/src/js/views/list/list_editable_renderer.js b/addons/web/static/src/js/views/list/list_editable_renderer.js index 9e83a45a5fc..0c6a9a4c9ce 100644 --- a/addons/web/static/src/js/views/list/list_editable_renderer.js +++ b/addons/web/static/src/js/views/list/list_editable_renderer.js @@ -175,7 +175,7 @@ ListRenderer.include({ if (field.readonly) { return true; } - var Widget = this.state.fieldAttrs[node.attrs.name].Widget; + var Widget = this.state.fieldsInfo[node.attrs.name].Widget; if (Widget && Widget.prototype.readonly) { return true; } @@ -268,7 +268,7 @@ ListRenderer.include({ return; } var $td = this._findTd($row, colIndex); - var widget = new this.state.fieldAttrs[name].Widget(this, name, record, { + var widget = new this.state.fieldsInfo[name].Widget(this, name, record, { mode: 'edit' }); if (widget.replace_element) { diff --git a/addons/web/static/src/js/views/list/list_renderer.js b/addons/web/static/src/js/views/list/list_renderer.js index 4d651198044..041fb2f6ee4 100644 --- a/addons/web/static/src/js/views/list/list_renderer.js +++ b/addons/web/static/src/js/views/list/list_renderer.js @@ -285,7 +285,7 @@ var ListRenderer = BasicRenderer.extend({ } var name = node.attrs.name; if (node.attrs.widget) { - var widget = new this.state.fieldAttrs[name].Widget(this, name, record, { + var widget = new this.state.fieldsInfo[name].Widget(this, name, record, { mode: 'readonly', }); widget.appendTo($td); @@ -505,7 +505,7 @@ var ListRenderer = BasicRenderer.extend({ } var description; if (node.attrs.widget) { - description = this.state.fieldAttrs[name].Widget.prototype.description; + description = this.state.fieldsInfo[name].Widget.prototype.description; } if (description === undefined) { description = node.attrs.string || field.string; 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 a616e9704df..4ec16ef35f4 100644 --- a/addons/web/static/src/js/views/list/list_view.js +++ b/addons/web/static/src/js/views/list/list_view.js @@ -27,15 +27,15 @@ var ListView = BasicView.extend({ /** * @override * - * @param {Object} arch - * @param {Object} fields + * @param {Object} viewInfo * @param {Object} params * @param {boolean} params.sidebar * @param {boolean} [params.hasSelectors=true] */ - init: function (arch, fields, params) { + init: function (viewInfo, params) { this._super.apply(this, arguments); + var arch = viewInfo.arch; this.controllerParams.editable = arch.attrs.editable; this.controllerParams.hasSidebar = params.sidebar; this.controllerParams.noContentHelp = params.action && params.action.help; 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 777d9bcb5a7..83bd32de830 100644 --- a/addons/web/static/src/js/views/pivot/pivot_view.js +++ b/addons/web/static/src/js/views/pivot/pivot_view.js @@ -31,9 +31,13 @@ var PivotView = AbstractView.extend({ * @override * @param {Object} params */ - init: function (arch, fields, params) { + 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 = []; @@ -82,7 +86,6 @@ var PivotView = AbstractView.extend({ if ((!activeMeasures.length) || arch.attrs.display_quantity) { activeMeasures.push('__count'); } - fields.__count = {string: _t("Count"), type: "integer"}; this.loadParams.measures = activeMeasures; this.loadParams.colGroupBys = colGroupBys; diff --git a/addons/web/static/src/js/views/view_dialogs.js b/addons/web/static/src/js/views/view_dialogs.js index b9870a78e56..2275830fe33 100644 --- a/addons/web/static/src/js/views/view_dialogs.js +++ b/addons/web/static/src/js/views/view_dialogs.js @@ -137,7 +137,7 @@ var FormViewDialog = ViewDialog.extend({ this._super(parent, options); }, /** - * If we changed some fieldAttrs, we need to restore them now. + * If we changed some fieldsInfo, we need to restore them now. */ destroy: function () { if (this.recordID && this.previousFieldProps) { @@ -166,15 +166,11 @@ var FormViewDialog = ViewDialog.extend({ fields_view_def = this.loadFieldView(this.dataset, this.options.view_id, 'form'); } - fields_view_def.then(function (fields_view) { + fields_view_def.then(function (viewInfo) { if (self.recordID) { - self.previousFieldProps = self.model.setFieldProps(self.recordID, { - fieldNames: Object.keys(fields_view.fields), - fields: fields_view.fields, - fieldAttrs: fields_view.fieldAttrs - }); + self.previousFieldProps = self.model.setFieldProps(self.recordID, viewInfo); } - var formview = new FormView(fields_view.arch, fields_view.fields, { + var formview = new FormView(viewInfo, { modelName: self.res_model, context: self.context, ids: self.res_id ? [self.res_id] : [], @@ -337,7 +333,7 @@ var SelectCreateDialog = ViewDialog.extend({ return $.when(searchDef).then(function (searchResult) { // Set the list view - var listView = new ListView(fields_views.list.arch, fields_views.list.fields, _.extend({ + var listView = new ListView(fields_views.list, _.extend({ context: searchResult.context, domain: searchResult.domain, groupBy: searchResult.groupBy, diff --git a/addons/web/static/src/js/widgets/domain_selector.js b/addons/web/static/src/js/widgets/domain_selector.js index 61b43c46ae7..670e620c2b3 100644 --- a/addons/web/static/src/js/widgets/domain_selector.js +++ b/addons/web/static/src/js/widgets/domain_selector.js @@ -383,7 +383,7 @@ var DomainLeaf = DomainNode.extend({ this.displayValue = this.value; try { var f = this.fieldSelector.selectedField; - if (!f.relation) { // TODO in this case, the value should be m2o input, etc... + if (f && !f.relation) { // TODO in this case, the value should be m2o input, etc... this.displayValue = field_utils.format[f.type](this.value, f); } } catch (err) {/**/} diff --git a/addons/web/static/test/fields/relational_fields_tests.js b/addons/web/static/test/fields/relational_fields_tests.js index 208a3c1023b..93104bbd516 100644 --- a/addons/web/static/test/fields/relational_fields_tests.js +++ b/addons/web/static/test/fields/relational_fields_tests.js @@ -1395,7 +1395,7 @@ QUnit.module('relational_fields', { rpcCount++; if (args.method === 'write') { assert.deepEqual(args.args[1].p, [[0, false, { - int_field: 123, product_id: 41 + display_name: false, int_field: 123, product_id: 41 }]]); } return this._super(route, args); @@ -2104,6 +2104,71 @@ QUnit.module('relational_fields', { form.destroy(); }); + QUnit.test('one2many (who contains a one2many) with tree view and without form view', function (assert) { + assert.expect(1); + + // avoid error in _postprocess + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form string="Partners">' + + '<group>' + + '<field name="turtles">' + + '<tree>' + + '<field name="partner_ids"/>' + + '</tree>' + + '</field>' + + '</group>' + + '</form>', + res_id: 1, + archs: { + "turtle,false,form": '<form string="Turtles"><field name="turtle_foo"/></form>', + }, + }); + + form.$('.o_data_row:first').click(); + + assert.strictEqual($('.modal .o_form_field[name="turtle_foo"]').text(), 'blip', + 'should open the modal and display the form field'); + + form.destroy(); + }); + + QUnit.test('one2many (who contains display_name) with tree view and without form view', function (assert) { + assert.expect(1); + + // avoid error in _fetchX2Manys + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form string="Partners">' + + '<group>' + + '<field name="turtles">' + + '<tree>' + + '<field name="display_name"/>' + + '</tree>' + + '</field>' + + '</group>' + + '</form>', + res_id: 1, + archs: { + "turtle,false,form": '<form string="Turtles"><field name="turtle_foo"/></form>', + }, + }); + + form.$('.o_data_row:first').click(); + + assert.strictEqual($('.modal .o_form_field[name="turtle_foo"]').text(), 'blip', + 'should open the modal and display the form field'); + + form.destroy(); + }); + + QUnit.module('FieldMany2Many'); QUnit.test('many2many kanban: edition', function (assert) { diff --git a/addons/web/static/test/helpers/test_utils.js b/addons/web/static/test/helpers/test_utils.js index ffc36095f62..3d274996ebd 100644 --- a/addons/web/static/test/helpers/test_utils.js +++ b/addons/web/static/test/helpers/test_utils.js @@ -15,6 +15,8 @@ var core = require('web.core'); var session = require('web.session'); var MockServer = require('web.MockServer'); var Widget = require('web.Widget'); +var DataManager = require('web.DataManager'); + /** * intercepts an event bubbling up the widget hierarchy. The event intercepted @@ -131,7 +133,13 @@ function createAsyncView(params) { _.extend(viewOptions, params.viewOptions); - var view = new params.View(fields_view.arch, fields_view.fields, viewOptions); + var viewInfo = DataManager.prototype._processFieldsView({ + type: fields_view.arch.tag, + arch: fields_view.arch, + fields: fields_view.fields, + }); + + var view = new params.View(viewInfo, viewOptions); $('#qunit-fixture').on('DOMNodeInserted.removeSRC', function () { removeSrcAttribute($(this), widget); @@ -298,7 +306,16 @@ function addMockEnvironment(widget, params) { arch = arch && (_.isObject(arch) ? arch : mockServer.fieldsViewGet(arch, model).arch); res[view_type] = { arch: arch, fields: params.data[model].fields }; }); - event.data.on_success(res); + + var views = _.mapObject(res, function (view, viewType) { + return DataManager.prototype._processFieldsView({ + type: viewType, + arch: view.arch, + fields: view.fields, + }); + }); + + event.data.on_success(views); }); intercept(widget, "get_session", function (event) { @@ -323,14 +340,14 @@ function addMockEnvironment(widget, params) { * @param {Class} params.Model the model class to use * @param {string[]} [params.fieldNames] the fields given to the model * @param {Object[]} [params.fields] map of field names to field description - * @param {Object[]} [params.fieldAttrs] map of field names to field attributes + * @param {Object[]} [params.fieldsInfo] map of field names to field attributes * @returns {Model} */ function createModel(params) { var widget = new Widget(); var model = new params.Model(widget, { - fieldAttrs: params.fieldAttrs || {}, + fieldsInfo: params.fieldsInfo, fieldNames: params.fieldNames, fields: params.fields, }); diff --git a/addons/web/static/test/views/basic_model_tests.js b/addons/web/static/test/views/basic_model_tests.js index 7c5e02501e8..7039611917c 100644 --- a/addons/web/static/test/views/basic_model_tests.js +++ b/addons/web/static/test/views/basic_model_tests.js @@ -310,6 +310,10 @@ QUnit.module('Views', { this.params.res_id = 1; this.params.fieldNames = ['category']; + this.params.fieldsInfo = {category: { + relatedFields: {display_name: {type: "char"}}, + fieldsInfo: {display_name: {}}, + }}; var model = createModel({ Model: BasicModel, @@ -319,9 +323,9 @@ QUnit.module('Views', { model.load(this.params).then(function (resultID) { var record = model.get(resultID); assert.strictEqual(record.data.category.data[0].res_id, 12, - "should have loaded many2omany res_ids"); + "should have loaded many2many res_ids"); assert.strictEqual(record.data.category.data[0].data.display_name, "gold", - "should have loaded many2omany display_name"); + "should have loaded many2many display_name"); record = model.get(resultID, {raw: true}); assert.deepEqual(record.data.category, [12], "with option raw, category should only return ids"); @@ -333,6 +337,10 @@ QUnit.module('Views', { assert.expect(2); this.params.fieldNames = ['category']; + this.params.fieldsInfo = {category: { + relatedFields: {date: {type: "date"}}, + fieldsInfo: {date: {}}, + }}; var model = createModel({ Model: BasicModel, @@ -909,7 +917,7 @@ QUnit.module('Views', { data: this.data, }); - this.params.fieldAttrs = { + this.params.fieldsInfo = { product_id: { context: "{'hello2': 'world', 'test2': foo}", domain: "[['hello2', 'like', 'world'], ['test2', 'like', foo]]", diff --git a/addons/web/static/test/views/form_tests.js b/addons/web/static/test/views/form_tests.js index 80c60c4bc20..a86ce81aebd 100644 --- a/addons/web/static/test/views/form_tests.js +++ b/addons/web/static/test/views/form_tests.js @@ -764,7 +764,7 @@ QUnit.module('Views', { mockRPC: function (route, args) { if (args.method === "onchange") { assert.deepEqual(args.args[3], - {"foo": "1", "p": "", "p.bar": "", "p.product_id": "", "timmy": "", "timmy.name": ""}, + {"foo": "1", "p": "", "p.bar": "", "p.display_name": "", "p.product_id": "", "timmy": "", "timmy.name": ""}, "should send only the fields used in the views"); } return this._super(route, args); @@ -1750,7 +1750,7 @@ QUnit.module('Views', { QUnit.test('attrs are properly transmitted to new records', function (assert) { assert.expect(2); - // this test checks that the fieldAttrs have been transmitted to the + // this test checks that the fieldsInfo have been transmitted to the // load function when creating a new record var terminology = { diff --git a/addons/web/static/test/views/kanban_tests.js b/addons/web/static/test/views/kanban_tests.js index 2b882cd7aa6..b4d91ebdb65 100644 --- a/addons/web/static/test/views/kanban_tests.js +++ b/addons/web/static/test/views/kanban_tests.js @@ -371,7 +371,7 @@ QUnit.module('Views', { arch: '<kanban class="o_kanban_test">' + '<templates><t t-name="kanban-box">' + '<div>' + - '<div class="o_kanban_tags_section"><field name="category_ids"/></div>' + + '<div class="o_kanban_tags_section"><field name="category_ids" widget="many2many_tags"/></div>' + '<field name="foo"/>' + '</div>' + '</t></templates>' + @@ -379,11 +379,11 @@ QUnit.module('Views', { mockRPC: function () { count++; return this._super.apply(this, arguments); - } + }, }); var $first_record = kanban.$('.o_kanban_record:first()'); - assert.strictEqual($first_record.find('.o_form_field_many2manytags span').length, 2, + assert.strictEqual($first_record.find('.o_form_field_many2manytags .badge').length, 2, 'first record should contain 2 tags'); assert.ok($first_record.find('span:first()').hasClass('o_tag_color_2'), 'first tag should have color 2'); diff --git a/addons/web/static/test/views/list_tests.js b/addons/web/static/test/views/list_tests.js index 700d65d6358..2a1fcaacb4c 100644 --- a/addons/web/static/test/views/list_tests.js +++ b/addons/web/static/test/views/list_tests.js @@ -621,10 +621,10 @@ QUnit.module('Views', { mockRPC: function (route, args) { rpcCount++; return this._super(route, args); - } + }, }); assert.strictEqual(rpcCount, 2, "should have done 2 rpcs: 1 searchread and 1 read for m2m"); - assert.ok(list.$('td:contains(Value 1, Value 2, Value 3)').length, + assert.ok(list.$('td:contains(3 records)').length, "should have a td with correct formatted value"); list.destroy(); }); diff --git a/addons/web_diagram/static/src/js/diagram_view.js b/addons/web_diagram/static/src/js/diagram_view.js index f92dc5a9ae3..e5906f032bb 100644 --- a/addons/web_diagram/static/src/js/diagram_view.js +++ b/addons/web_diagram/static/src/js/diagram_view.js @@ -28,13 +28,13 @@ var DiagramView = BasicView.extend({ }, /** * @override - * @param {Object} arch - * @param {Object} fields + * @param {Object} viewInfo * @param {Object} params */ - init: function (arch, fields, params) { + init: function (viewInfo, params) { this._super.apply(this, arguments); var self = this; + var arch = viewInfo.arch; // Compute additional data for diagram model function toTitleCase(str) { return str.replace(/\w\S*/g, function (txt) { |
