diff --git a/addons/mail/static/src/js/basic_view.js b/addons/mail/static/src/js/basic_view.js index 383d5e68f4e..a019709dad4 100644 --- a/addons/mail/static/src/js/basic_view.js +++ b/addons/mail/static/src/js/basic_view.js @@ -6,11 +6,12 @@ var BasicView = require('web.BasicView'); var mailWidgets = ['mail_followers', 'mail_thread', 'mail_activity', 'kanban_activity']; BasicView.include({ - init: function (viewInfo, params) { + init: function (viewInfo) { this._super.apply(this, arguments); this.mailFields = {}; - for (var fieldName in viewInfo.fieldsInfo) { - var fieldInfo = viewInfo.fieldsInfo[fieldName]; + var fieldsInfo = viewInfo.fieldsInfo[this.viewType]; + for (var fieldName in fieldsInfo) { + var fieldInfo = fieldsInfo[fieldName]; if (_.contains(mailWidgets, fieldInfo.widget)) { this.mailFields[fieldInfo.widget] = fieldName; fieldInfo.__no_fetch = true; diff --git a/addons/mail/static/src/js/chatter.js b/addons/mail/static/src/js/chatter.js index 1d39f85664d..9f448152422 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.fieldsInfo[mailFields.mail_thread].options; + var nodeOptions = this.record.fieldsInfo.form[mailFields.mail_thread].options; this.hasLogButton = nodeOptions.display_log_button; } }, diff --git a/addons/web/static/src/js/fields/abstract_field.js b/addons/web/static/src/js/fields/abstract_field.js index 01de5fb334c..47802d5d8c7 100644 --- a/addons/web/static/src/js/fields/abstract_field.js +++ b/addons/web/static/src/js/fields/abstract_field.js @@ -92,9 +92,14 @@ var AbstractField = Widget.extend({ // such as the type, the comodel (relation), ... this.field = record.fields[name]; + // the 'viewType' is the type of the view in which the field widget is + // instantiated. For standalone widgets, a 'default' viewType is set. + this.viewType = options.viewType || 'default'; + // the 'attrs' property contains the attributes of the xml 'field' tag, // the inner views... - this.attrs = record.fieldsInfo && record.fieldsInfo[name] || {}; + var fieldsInfo = record.fieldsInfo[this.viewType]; + this.attrs = fieldsInfo && fieldsInfo[name] || {}; // modifiers this.modifiers = JSON.parse(this.attrs.modifiers || "{}"); diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index 7cd3ce8598e..e4ea88db789 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -99,6 +99,9 @@ var FieldMany2One = AbstractField.extend({ quick_create: true, }); this.m2o_value = field_utils.format.many2one(this.value); + // 'recordParams' is a dict of params used when calling functions + // 'getDomain' and 'getContext' on this.record + this.recordParams = {fieldName: this.name, viewType: this.viewType}; }, start: function () { // what is 'this.floating' for? add a comment! @@ -201,7 +204,7 @@ var FieldMany2One = AbstractField.extend({ model: this.field.relation, method: 'name_create', args: [name], - context: this.record.getContext({fieldName: this.name}) + context: this.record.getContext(this.recordParams), }) .then(function (result) { if (self.mode === "edit") { @@ -261,8 +264,8 @@ var FieldMany2One = AbstractField.extend({ var def = $.Deferred(); this.orderer.add(def); - var context = this.record.getContext({fieldName: this.name}); - var domain = this.record.getDomain({fieldName: this.name}); + var context = this.record.getContext(this.recordParams); + var domain = this.record.getDomain(this.recordParams); var blacklisted_ids = this._getSearchBlacklist(); if (blacklisted_ids.length > 0) { @@ -356,7 +359,7 @@ var FieldMany2One = AbstractField.extend({ new dialogs.SelectCreateDialog(this, _.extend({}, this.nodeOptions, { res_model: this.field.relation, domain: this.record.getDomain({fieldName: this.name}), - context: _.extend({}, this.record.getContext({fieldName: this.name}), context || {}), + context: _.extend({}, this.record.getContext(this.recordParams), context || {}), title: (view === 'search' ? _t("Search: ") : _t("Create: ")) + this.string, initial_ids: ids ? _.map(ids, function (x) { return x[0]; }) : undefined, initial_view: view, @@ -394,7 +397,7 @@ var FieldMany2One = AbstractField.extend({ model: this.field.relation, method: 'get_formview_action', args: [[this.value.res_id]], - context: this.record.getContext({fieldName: this.name}), + context: this.record.getContext(this.recordParams), }) .then(function (action) { self.trigger_up('do_action', {action: action}); @@ -410,7 +413,7 @@ var FieldMany2One = AbstractField.extend({ return; } var self = this; - var context = this.record.getContext({fieldName: this.name}); + var context = this.record.getContext(this.recordParams); this._rpc({ model: this.field.relation, method: 'get_formview_id', @@ -531,6 +534,7 @@ var FieldX2Many = AbstractField.extend({ this.isReadonly = this.mode === 'readonly'; this.view = this.attrs.views.list || this.attrs.views.kanban; this.activeActions = {}; + this.recordParams = {fieldName: this.name, viewType: this.viewType}; var arch = this.view && this.view.arch; if (arch) { this.activeActions.create = arch.attrs.create ? @@ -780,8 +784,8 @@ var FieldOne2Many = FieldX2Many.extend({ */ _openFormDialog: function (params) { this.trigger_up('open_one2many_record', _.extend(params, { - domain: this.record.getDomain({fieldName: this.name}), - context: this.record.getContext({fieldName: this.name}), + domain: this.record.getDomain(this.recordParams), + context: this.record.getContext(this.recordParams), field: this.field, fields_view: this.attrs.views && this.attrs.views.form, viewInfo: this.view, @@ -873,7 +877,7 @@ var FieldMany2Many = FieldX2Many.extend({ new dialogs.SelectCreateDialog(this, { res_model: this.field.relation, domain: domain.concat(["!", ["id", "in", this.value.res_ids]]), - context: this.record.getContext({fieldName: this.name}), + context: this.record.getContext(this.recordParams), title: _t("Add: ") + this.string, no_create: this.nodeOptions.no_create || !this.activeActions.create, fields_view: this.attrs.views.form, @@ -900,8 +904,8 @@ var FieldMany2Many = FieldX2Many.extend({ */ _onOpenRecord: function (event) { _.extend(event.data, { - context: this.record.getContext({fieldName: this.name}), - domain: this.record.getDomain({fieldName: this.name}), + context: this.record.getContext(this.recordParams), + domain: this.record.getDomain(this.recordParams), fields_view: this.attrs.views && this.attrs.views.form, on_saved: this.trigger_up.bind(this, 'reload', {db_id: event.data.id}), readonly: this.mode === 'readonly', @@ -976,6 +980,7 @@ var FieldMany2ManyTags = AbstractField.extend({ this.many2one = new FieldMany2One(this, this.name, this.record, { idForLabel: this.idForLabel, mode: 'edit', + viewType: this.viewType, }); // to prevent the M2O to take the value of the M2M this.many2one.value = false; diff --git a/addons/web/static/src/js/services/data_manager.js b/addons/web/static/src/js/services/data_manager.js index fa3a2efb231..1a4cebdc955 100644 --- a/addons/web/static/src/js/services/data_manager.js +++ b/addons/web/static/src/js/services/data_manager.js @@ -280,6 +280,7 @@ return core.Class.extend({ // 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)); }); @@ -317,10 +318,12 @@ return core.Class.extend({ display_name: {type: 'char'}, //id: {type: 'integer'}, }; - attrs.fieldsInfo = {display_name: {}, id: {}}; + attrs.fieldsInfo = {}; + attrs.fieldsInfo.default = {display_name: {}, id: {}}; + attrs.viewType = 'default'; if (attrs.color || 'color') { - attrs.relatedFields[attrs.color || 'color'] = {type: 'int'}; - attrs.fieldsInfo.color = {}; + attrs.relatedFields[attrs.color || 'color'] = {type: 'integer'}; + attrs.fieldsInfo.default.color = {}; } } } @@ -359,12 +362,14 @@ return core.Class.extend({ * @returns {Object} viewInfo */ _processFieldsView: function (viewInfo) { - viewInfo.fieldsInfo = this._processFields(viewInfo.type, viewInfo.arch, viewInfo.fields); - // by default display fetch display_name and id + var viewFields = this._processFields(viewInfo.type, viewInfo.arch, viewInfo.fields); + // by default fetch display_name and id if (!viewInfo.fields.display_name) { viewInfo.fields.display_name = {type: 'char'}; - viewInfo.fieldsInfo.display_name = {}; + viewFields.display_name = {}; } + viewInfo.fieldsInfo = {}; + viewInfo.fieldsInfo[viewInfo.type] = viewFields; utils.deepFreeze(viewInfo.fields); return viewInfo; }, 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 b4d343d7eb0..00e2fc43f6d 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -23,7 +23,6 @@ odoo.define('web.BasicModel', function (require) { * count: {integer}, * data: {Object|Object[]}, * domain: {*[]}, - * fieldNames: {string[]}, * fields: {Object}, * fieldsInfo: {Object}, * getContext: {function}, @@ -145,7 +144,7 @@ var BasicModel = AbstractModel.extend({ /** * Add a default record to a list object. This method actually make a new - * record with the makeDefaultRecord method, then add it to the list object. + * record with the _makeDefaultRecord method, then add it to the list object. * * @param {string} listID a valid handle for a list object * @param {Object} [options] @@ -160,13 +159,13 @@ var BasicModel = AbstractModel.extend({ var position = (options && options.position) || 'top'; var params = { context: context, - fieldsInfo: list.fieldsInfo, - fieldNames: list.fieldNames, fields: list.fields, + fieldsInfo: list.fieldsInfo, parentID: list.id, relationField: list.relationField, + viewType: list.viewType, }; - return this.makeDefaultRecord(list.model, params).then(function (id) { + return this._makeDefaultRecord(list.model, params).then(function (id) { list.count++; if (position === 'top') { list.data.unshift(id); @@ -243,11 +242,11 @@ var BasicModel = AbstractModel.extend({ record.res_ids.splice(index + 1, 0, res_id); return self.load({ fieldsInfo: record.fieldsInfo, - fieldNames: record.fieldNames, fields: record.fields, modelName: record.model, res_id: res_id, res_ids: record.res_ids.slice(0), + viewType: record.viewType, }); }); }, @@ -400,8 +399,6 @@ var BasicModel = AbstractModel.extend({ * * @param {any} params * @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 * @param {string} [params.type] 'record' or 'list' * @param {string} [params.recordID] an ID for an existing resource. @@ -409,197 +406,26 @@ var BasicModel = AbstractModel.extend({ */ load: function (params) { params.type = params.type || (params.res_id !== undefined ? 'record' : 'list'); + // FIXME: the following seems only to be used by the basic_model_tests + // so it should probably be removed and the tests should be adapted + params.viewType = params.viewType || 'default'; + if (!params.fieldsInfo) { + var fieldsInfo = {}; + for (var fieldName in params.fieldNames) { + fieldsInfo[params.fieldNames[fieldName]] = {}; + } + params.fieldsInfo = {}; + params.fieldsInfo[params.viewType] = fieldsInfo; + } if (params.type === 'record' && params.res_id === undefined) { - return this.makeDefaultRecord(params.modelName, params); + return this._makeDefaultRecord(params.modelName, params); } var dataPoint = this._makeDataPoint(params); return this._load(dataPoint).then(function () { return dataPoint.id; }); }, - /** - * When one needs to create a record from scratch, a not so simple process - * needs to be done: - * - call the /default_get route to get default values - * - fetch all relational data - * - apply all onchanges if necessary - * - fetch all relational data - * - * This method tries to optimize the process as much as possible. Also, - * it is quite horrible and should be refactored at some point. - * - * @param {any} params - * @param {string} modelName model name - * @param {Object} params.context the context for the new record - * @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 - * @param {Object} params.context the context for the new record - * @returns {Deferred -> string} resolves to the id for the created resource - */ - makeDefaultRecord: function (modelName, params) { - var self = this; - var fields_key = _.without(params.fieldNames, '__last_update'); - - return this._rpc({ - model: modelName, - method: 'default_get', - args: [fields_key], - context: params.context, - }) - .then(function (result) { - // fill default values for missing fields - for (var i = 0; i < params.fieldNames.length; i++) { - var fieldName = params.fieldNames[i]; - if (!(fieldName in result)) { - var field = params.fields[fieldName]; - if (field.type === 'one2many' || field.type === 'many2many') { - result[fieldName] = []; - } else { - result[fieldName] = null; - } - } - } - - var data = {}; - var record = self._makeDataPoint({ - modelName: modelName, - data: data, - fields: params.fields, - fieldNames: params.fieldNames, - fieldsInfo: params.fieldsInfo, - context: params.context, - parentID: params.parentID, - relationField: params.relationField, - res_ids: params.res_ids, - }); - - var defs = []; // FIXME: remove defs? - _.each(params.fieldNames, function (name) { - var field = params.fields[name]; - data[name] = null; - record._changes = record._changes || {}; - if (field.type === 'many2one' && result[name]) { - var rec = self._makeDataPoint({ - context: record.context, - data: {id: result[name]}, - modelName: field.relation, - }); - record._changes[name] = rec.id; - } else if (field.type === 'one2many' || field.type === 'many2many') { - var fieldInfo = record.fieldsInfo[name]; - var view = fieldInfo.views && fieldInfo.views[fieldInfo.mode]; - - var x2manyList = self._makeDataPoint({ - context: record.context, - fieldsInfo: fieldInfo.fieldsInfo || view && view.fieldsInfo, - fields: fieldInfo.relatedFields || view && view.fields, - limit: fieldInfo.limit, - modelName: field.relation, - parentID: record.id, - rawContext: fieldInfo && fieldInfo.context, - relationField: field.relation_field, - res_ids: [], - static: true, - type: 'list', - }); - record._changes[name] = x2manyList.id; - var many2ones = {}; - var r; - _.each(result[name], function (value) { - if (_.isArray(value)) { - // value is a command - if (value[0] === 0) { - // CREATE - r = self._makeDataPoint({ - modelName: x2manyList.model, - context: x2manyList.context, - fieldsInfo: fieldInfo.fieldsInfo || view && view.fieldsInfo, - fields: fieldInfo.relatedFields || view && view.fields, - }); - x2manyList._changes.push(r.id); - r._changes = value[2]; - - // this is necessary so the fields are initialized - for (var fieldName in value[2]) { - r.data[fieldName] = null; - } - - for (var name in r._changes) { - if (r.fields[name].type === 'many2one') { - var rec = self._makeDataPoint({ - context: r.context, - modelName: r.fields[name].relation, - data: {id: r._changes[name]} - }); - r._changes[name] = rec.id; - many2ones[name] = true; - } - } - } - if (value[0] === 6) { - // REPLACE_WITH - x2manyList._changes = []; - } - } else { - // value is an id - r = self._makeDataPoint({ - modelName: x2manyList.model, - context: x2manyList.context, - fieldsInfo: fieldInfo.fieldsInfo || view && view.fieldsInfo, - fields: fieldInfo.relatedFields || view && view.fields, - res_id: value, - }); - if (!x2manyList._changes) { - x2manyList._changes = []; - } - x2manyList._changes.push(r.id); - } - }); - - // fetch many2ones display_name - _.each(_.keys(many2ones), function (name) { - defs.push(self._fetchNameGets(x2manyList, name)); - }); - } else if (field.type === 'date') { - // process date: convert into a moment instance - record._changes[name] = fieldUtils.parse.date(result[name]); - } else if (field.type === 'datetime') { - // process datetime: convert into a moment instance - record._changes[name] = fieldUtils.parse.datetime(result[name]); - } else { - record._changes[name] = result[name]; - } - }); - return $.when.apply($, defs) - .then(function () { - var shouldApplyOnchange = false; - var field; - for (var field_name in record.data) { - field = record.fields[field_name]; - if (field.onChange) { - shouldApplyOnchange = true; - } - } - if (shouldApplyOnchange) { - return self._applyOnChange(record, fields_key); - } else { - return $.when(); - } - }) - .then(function () { - return self._fetchRelationalData(record); - }) - .then(function () { - return self._postprocess(record); - }) - .then(function () { - return record.id; - }); - }); - }, /** * This helper method is designed to help developpers that want to use a * field widget outside of a view. In that case, we want a way to create @@ -607,7 +433,7 @@ var BasicModel = AbstractModel.extend({ * * @param {string} model name of the model * @param {Object[]} fields a description of field properties - * @param {Object} [fieldInfo] various field fieldInfo that we want to set + * @param {Object} [fieldInfo] various field info that we want to set * @returns {string} the local id for the created resource */ makeRecord: function (model, fields, fieldInfo) { @@ -616,10 +442,15 @@ var BasicModel = AbstractModel.extend({ _.each(fields, function (field) { record_fields[field.name] = _.pick(field, 'type', 'relation', 'domain'); }); + var viewInfo = {}; + viewInfo.default = fieldInfo || _.mapObject(fields, function () { + return {}; + }); var record = this._makeDataPoint({ modelName: model, fields: record_fields, - fieldsInfo: fieldInfo, + fieldsInfo: viewInfo, + viewType: 'default', }); _.each(fields, function (field) { if ('value' in field) { @@ -670,10 +501,10 @@ var BasicModel = AbstractModel.extend({ var params = { context: element.context, fieldsInfo: element.fieldsInfo, - fieldNames: element.fieldNames, fields: element.fields, + viewType: element.viewType, }; - return this.makeDefaultRecord(element.model, params); + return this._makeDefaultRecord(element.model, params); } this.discardChanges(id); } @@ -703,7 +534,7 @@ var BasicModel = AbstractModel.extend({ if (element.type === 'record') { element.offset = _.indexOf(element.res_ids, element.res_id); } - var loadOptions = _.pick(options, 'fieldNames'); + var loadOptions = _.pick(options, 'fieldNames', 'viewType'); return this._load(element, loadOptions).then(function (result) { return result.id; }); @@ -762,7 +593,8 @@ var BasicModel = AbstractModel.extend({ } if (method === 'create') { - _.each(record.fieldNames, function (name) { + var fieldNames = record.getFieldNames(); + _.each(fieldNames, function (name) { changes[name] = changes[name] || record.data[name]; if (changes[name] === null) { delete changes[name]; @@ -799,34 +631,19 @@ var BasicModel = AbstractModel.extend({ }); }, /** - * Set fieldNames. - * Set fields on a record element (current fields as defaults) - * 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. + * Completes the fields and fieldsInfo of a dataPoint with the given ones. + * It is useful for the cases where a record element is shared between + * various views, such as a one2many with a tree and a form view. * * @param {string} recordID a valid element ID - * @param {Object} props - * @param {Object} props.fieldNames - * @param {Object} props.fields - * @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.fieldsInfo the previous field fieldInfo + * @param {Object} viewInfo + * @param {Object} viewInfo.fields + * @param {Object} viewInfo.fieldsInfo */ - setFieldProps: function (recordID, props) { + addFieldsInfo: function (recordID, viewInfo) { var record = this.localData[recordID]; - var result = { - fieldNames: record.fieldNames, - fields: record.fields, - fieldsInfo: record.fieldsInfo, - }; - record.mode = props.mode; - record.fieldNames = Object.keys(props.fieldsInfo); - record.fields = _.defaults(props.fields, record.fields); - record.fieldsInfo = props.fieldsInfo; - return result; + record.fields = _.defaults(record.fields, viewInfo.fields); + record.fieldsInfo = _.defaults(record.fieldsInfo, viewInfo.fieldsInfo); }, /** * For list resources, this changes the orderedBy key, then performs the @@ -1029,7 +846,8 @@ var BasicModel = AbstractModel.extend({ var commands = this._generateX2ManyCommands(record); // compute field values - var currentData = _.pick(_.extend(rawData, commands), record.fieldNames); + var fieldNames = record.getFieldNames(); + var currentData = _.pick(_.extend(rawData, commands), fieldNames); if (record.relationField) { // if there is a relation field, this means that record is an elem // in a one2many. The relation field is the corresponding many2one @@ -1099,16 +917,16 @@ var BasicModel = AbstractModel.extend({ fields: list.fields, fieldsInfo: list.fieldsInfo, modelName: list.model, - fieldNames: _.keys(command[2]), + viewType: list.viewType, }; if (command[0] === 1) { params.res_id = command[1]; } rec = self._makeDataPoint(params); var data = command[2]; - _.each(rec.fieldNames, function (name) { + _.each(Object.keys(data), function (name) { var field = rec.fields[name]; - if (field.type === 'many2one') { + if (field && field.type === 'many2one') { var r = self._makeDataPoint({ modelName: field.relation, data: { @@ -1161,7 +979,7 @@ 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 fieldInfo = record.fieldsInfo[record.viewType][fieldName]; var view = fieldInfo.views && fieldInfo.views[fieldInfo.mode]; var rec; var defs = []; @@ -1179,7 +997,10 @@ var BasicModel = AbstractModel.extend({ // list's fields with those in the created record var newRecord = this.localData[command.id]; _.defaults(list.fields, newRecord.fields); + _.defaults(list.fieldsInfo, newRecord.fieldsInfo); newRecord.fields = list.fields; + newRecord.fieldsInfo = list.fieldsInfo; + newRecord.viewType = list.viewType; list._changes.push(newRecord.id); break; case 'ADD_M2M': @@ -1193,9 +1014,10 @@ var BasicModel = AbstractModel.extend({ rec = self._makeDataPoint({ context: record.context, modelName: field.relation, - fields: fieldInfo.relatedFields || view && view.fields, - fieldsInfo: fieldInfo.fieldsInfo || view && view.fieldsInfo, + fields: view ? view.fields : fieldInfo.relatedFields, + fieldsInfo: view ? view.fieldsInfo : fieldInfo.fieldsInfo, res_id: d.id, + viewType: view ? view.type : fieldInfo.viewType, }); list_records[d.id] = rec; list._changes.push(rec.id); @@ -1205,15 +1027,16 @@ var BasicModel = AbstractModel.extend({ // _changes so this is a very specific case) // this could be optimized by registering the fetched records in the list's _cache // so that if a record is removed and then re-added, it won't be fetched twice + var fieldNames = list.getFieldNames(); var def = this._rpc({ model: list.model, method: 'read', - args: [_.pluck(data, 'id'), list.fieldNames] + args: [_.pluck(data, 'id'), fieldNames] }) .then(function (records) { _.each(records, function (record) { list_records[record.id].data = record; - self._parseServerData(list.fieldNames, list.fields, record); + self._parseServerData(fieldNames, list.fields, record); }); }); defs.push(def); @@ -1272,12 +1095,12 @@ var BasicModel = AbstractModel.extend({ _buildOnchangeSpecs: function (record) { // TODO: replace this function by some generic tree function in utils var specs = {}; - _.each(record.fieldNames, function (name) { + _.each(record.getFieldNames(), function (name) { var field = record.fields[name]; - var fieldInfo = record.fieldsInfo[name]; + var fieldInfo = record.fieldsInfo[record.viewType][name]; specs[name] = (field.onChange) || ""; _.each(fieldInfo.views, function (view) { - _.each(view.fieldsInfo, function (field, subname) { + _.each(view.fieldsInfo[view.type], function (field, subname) { specs[name + '.' + subname] = (view.fields[subname].onChange) || ""; }); }); @@ -1335,13 +1158,18 @@ var BasicModel = AbstractModel.extend({ * For a given resource of type 'record', fetch all data. * * @param {Object} record local resource - * @param {string[]} [fieldNames] the list of fields to fetch. If not given, - * fetch all the fields in record.fieldNames (+ display_name) + * @param {Object} [options] + * @param {string[]} [options.fieldNames] the list of fields to fetch. If + * not given, fetch all the fields in record.fieldNames (+ display_name) + * @param {string} [optinos.viewType] the type of view for which the record + * is fetched (usefull to load the adequate fields), by defaults, uses + * record.viewType * @returns {Deferred -> Object} resolves to the record */ - _fetchRecord: function (record, fieldNames) { + _fetchRecord: function (record, options) { var self = this; - fieldNames = fieldNames || _.uniq(record.fieldNames.concat(['display_name'])); + var fieldNames = options && options.fieldNames || + _.uniq(record.getFieldNames().concat(['display_name'])); return this._rpc({ model: record.model, method: 'read', @@ -1356,7 +1184,7 @@ var BasicModel = AbstractModel.extend({ self._parseServerData(fieldNames, record.fields, record.data); }) .then(function () { - return self._fetchX2Manys(record, fieldNames).then(function () { + return self._fetchX2Manys(record, options).then(function () { return self._postprocess(record); }); }); @@ -1369,7 +1197,7 @@ var BasicModel = AbstractModel.extend({ * batching the requests if possible (for example, if 3 many2ones are in * relation on the same model, then we can probably fetch them in one rpc) * - * This method is currently only called by makeDefaultRecord, it should be + * This method is currently only called by _makeDefaultRecord, it should be * called by the onchange methods at some point. * * @todo fix bug: returns a list of deferred, not a deferred @@ -1382,7 +1210,7 @@ var BasicModel = AbstractModel.extend({ var toBeFetched = []; // find all many2one related records to be fetched - _.each(record.fieldNames, function (name) { + _.each(record.getFieldNames(), function (name) { var field = record.fields[name]; if (field.type === 'many2one') { var localId = (record._changes && record._changes[name]) || record.data[name]; @@ -1417,8 +1245,8 @@ var BasicModel = AbstractModel.extend({ _fetchSpecialData: function (record) { var self = this; var fieldNames = []; - return $.when.apply($, _.map(record.fieldNames, function (name) { - var fieldInfo = record.fieldsInfo[name] || {}; + return $.when.apply($, _.map(record.getFieldNames(), function (name) { + var fieldInfo = record.fieldsInfo[record.viewType][name] || {}; var Widget = fieldInfo.Widget; if (Widget && Widget.prototype.specialData) { return self[Widget.prototype.specialData](record, name).then(function (data) { @@ -1551,7 +1379,7 @@ var BasicModel = AbstractModel.extend({ * the undefined value. */ _fetchSpecialStatus: function (record, fieldName) { - var foldField = record.fieldsInfo[fieldName].options.fold_field; + var foldField = record.fieldsInfo[record.viewType][fieldName].options.fold_field; var fieldsToRead = foldField ? [foldField] : []; return this._fetchSpecialMany2ones(record, fieldName, fieldsToRead).then(function (m2os) { _.each(m2os, function (m2o) { @@ -1575,7 +1403,7 @@ var BasicModel = AbstractModel.extend({ _fetchSpecialDomain: function (record, fieldName) { var context = record.getContext({fieldName: fieldName}); - var domainModel = record.fieldsInfo[fieldName].options.model; + var domainModel = record.fieldsInfo[record.viewType][fieldName].options.model; if (record.data.hasOwnProperty(domainModel)) { domainModel = record._changes && record._changes[domainModel] || record.data[domainModel]; } @@ -1642,24 +1470,31 @@ var BasicModel = AbstractModel.extend({ * useless rpcs. * * @param {Object} record local resource - * @param {string[]} [fieldNames] list of fields to fetch + * @param {Object} [options] + * @param {string[]} [options.fieldNames] the list of fields to fetch. + * If not given, fetch all the fields in record.fieldNames + * @param {string} [options.viewType] the type of view for which the main + * record is fetched (useful to load the adequate fields), by defaults, + * uses record.viewType * @returns {Deferred} */ - _fetchX2Manys: function (record, fieldNames) { + _fetchX2Manys: function (record, options) { var self = this; var defs = []; - fieldNames = fieldNames || record.fieldNames; + var fieldNames = options && options.fieldNames || record.getFieldNames(); + var viewType = options && options.viewType || record.viewType; _.each(fieldNames, function (fieldName) { var field = record.fields[fieldName]; if (field.type === 'one2many' || field.type === 'many2many') { - var fieldInfo = record.fieldsInfo[fieldName]; + var fieldInfo = record.fieldsInfo[viewType][fieldName]; var rawContext = fieldInfo && fieldInfo.context; var view = fieldInfo.views && fieldInfo.views[fieldInfo.mode]; + var fieldsInfo = view ? view.fieldsInfo : (fieldInfo.fieldsInfo || {}); var ids = record.data[fieldName] || []; var list = self._makeDataPoint({ count: ids.length, - fieldsInfo: fieldInfo.fieldsInfo || view && view.fieldsInfo, - fields: fieldInfo.relatedFields || view && view.fields, + fieldsInfo: fieldsInfo, + fields: view ? view.fields : fieldInfo.relatedFields, limit: fieldInfo.limit, modelName: field.relation, res_ids: ids, @@ -1668,6 +1503,7 @@ var BasicModel = AbstractModel.extend({ parentID: record.id, rawContext: rawContext, relationField: field.relation_field, + viewType: view ? view.type : fieldInfo.viewType, }); record.data[fieldName] = list.id; if (!fieldInfo.__no_fetch) { @@ -1691,7 +1527,7 @@ var BasicModel = AbstractModel.extend({ _fetchX2ManyBatched: function (list, fieldName) { var self = this; var field = list.fields[fieldName]; - var fieldInfo = list.fieldsInfo[fieldName]; + var fieldInfo = list.fieldsInfo[list.viewType][fieldName]; var view = fieldInfo.views && fieldInfo.views[fieldInfo.mode]; // step 1: collect ids @@ -1755,10 +1591,11 @@ var BasicModel = AbstractModel.extend({ */ _fetchX2ManysBatched: function (list) { var defs = []; - for (var i = 0; i < list.fieldNames.length; i++) { - var field = list.fields[list.fieldNames[i]]; + var fieldNames = list.getFieldNames(); + for (var i = 0; i < fieldNames.length; i++) { + var field = list.fields[fieldNames[i]]; if (field.type === 'many2many' || field.type === 'one2many') { - defs.push(this._fetchX2ManyBatched(list, list.fieldNames[i])); + defs.push(this._fetchX2ManyBatched(list, fieldNames[i])); } } return $.when.apply($, defs); @@ -1853,7 +1690,8 @@ var BasicModel = AbstractModel.extend({ context.set_eval_context(this._getEvalContext(element)); if (options && options.fieldName) { - var fieldInfo = element.fieldsInfo[options.fieldName]; + var viewType = options.viewType || element.viewType; + var fieldInfo = element.fieldsInfo[viewType][options.fieldName]; if (fieldInfo && fieldInfo.context) { context.add(fieldInfo.context); } else { @@ -1885,7 +1723,8 @@ var BasicModel = AbstractModel.extend({ */ _getDomain: function (element, options) { if (options && options.fieldName) { - var fieldInfo = element.fieldsInfo[options.fieldName]; + var viewType = options.viewType || element.viewType; + var fieldInfo = element.fieldsInfo[viewType][options.fieldName]; if (fieldInfo && fieldInfo.domain) { return Domain.prototype.stringToArray( fieldInfo.domain, @@ -1930,6 +1769,16 @@ var BasicModel = AbstractModel.extend({ } return evalContext; }, + /** + * Returns the list of field names of the given element according to its + * default view type. + * + * @param {Object} element an element from the localData + * @returns {string[]} the list of field names + */ + _getFieldNames: function (element) { + return Object.keys(element.fieldsInfo[element.viewType] || {}); + }, /** * Helper method for the load entry point. * @@ -1942,7 +1791,7 @@ var BasicModel = AbstractModel.extend({ */ _load: function (dataPoint, options) { if (dataPoint.type === 'record') { - return this._fetchRecord(dataPoint, options && options.fieldNames); + return this._fetchRecord(dataPoint, options); } if (dataPoint.type === 'list' && dataPoint.groupedBy.length) { return this._readGroup(dataPoint); @@ -1979,13 +1828,6 @@ 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, @@ -1994,9 +1836,8 @@ var BasicModel = AbstractModel.extend({ count: params.count || res_ids.length, data: params.data || (type === 'record' ? {} : []), domain: params.domain || [], - fieldNames: params.fieldNames || Object.keys(params.fieldsInfo), fields: fields, - fieldsInfo: params.fieldsInfo || {}, + fieldsInfo: params.fieldsInfo, groupedBy: params.groupedBy || [], id: _.uniqueId(params.modelName + '_'), isOpen: params.isOpen, @@ -2015,15 +1856,207 @@ var BasicModel = AbstractModel.extend({ static: params.static || false, type: type, // 'record' | 'list' value: value, + viewType: params.viewType, }; dataPoint.getContext = this._getContext.bind(this, dataPoint); dataPoint.getDomain = this._getDomain.bind(this, dataPoint); + dataPoint.getFieldNames = this._getFieldNames.bind(this, dataPoint); this.localData[dataPoint.id] = dataPoint; return dataPoint; }, + /** + * When one needs to create a record from scratch, a not so simple process + * needs to be done: + * - call the /default_get route to get default values + * - fetch all relational data + * - apply all onchanges if necessary + * - fetch all relational data + * + * This method tries to optimize the process as much as possible. Also, + * it is quite horrible and should be refactored at some point. + * + * @private + * @param {any} params + * @param {string} modelName model name + * @param {Object} params.context the context for the new record + * @param {Object} params.fieldsInfo contains the fieldInfo of each view, + * for each field + * @param {Object} params.fields contains the description of each field + * @param {Object} params.context the context for the new record + * @param {string} params.viewType the key in fieldsInfo of the fields to load + * @returns {Deferred -> string} resolves to the id for the created resource + */ + _makeDefaultRecord: function (modelName, params) { + var self = this; + var fieldNames = Object.keys(params.fieldsInfo[params.viewType]); + var fields_key = _.without(fieldNames, '__last_update'); + + return this._rpc({ + model: modelName, + method: 'default_get', + args: [fields_key], + context: params.context, + }) + .then(function (result) { + // fill default values for missing fields + for (var i = 0; i < fieldNames.length; i++) { + var fieldName = fieldNames[i]; + if (!(fieldName in result)) { + var field = params.fields[fieldName]; + if (field.type === 'one2many' || field.type === 'many2many') { + result[fieldName] = []; + } else { + result[fieldName] = null; + } + } + } + + var data = {}; + var record = self._makeDataPoint({ + modelName: modelName, + data: data, + fields: params.fields, + fieldsInfo: params.fieldsInfo, + context: params.context, + parentID: params.parentID, + relationField: params.relationField, + res_ids: params.res_ids, + viewType: params.viewType, + }); + + var defs = []; // FIXME: remove defs? + _.each(fieldNames, function (name) { + var field = params.fields[name]; + data[name] = null; + record._changes = record._changes || {}; + if (field.type === 'many2one' && result[name]) { + var rec = self._makeDataPoint({ + context: record.context, + data: {id: result[name]}, + modelName: field.relation, + }); + record._changes[name] = rec.id; + } else if (field.type === 'one2many' || field.type === 'many2many') { + var fieldInfo = record.fieldsInfo[record.viewType][name]; + var view = fieldInfo.views && fieldInfo.views[fieldInfo.mode]; + var fieldsInfo = view ? view.fieldsInfo : fieldInfo.fieldsInfo; + var fields = view ? view.fields : fieldInfo.relatedFields; + var viewType = view ? view.type : fieldInfo.viewType; + + var x2manyList = self._makeDataPoint({ + context: record.context, + fieldsInfo: fieldsInfo, + fields: fields, + limit: fieldInfo.limit, + modelName: field.relation, + parentID: record.id, + rawContext: fieldInfo && fieldInfo.context, + relationField: field.relation_field, + res_ids: [], + static: true, + type: 'list', + viewType: viewType, + }); + record._changes[name] = x2manyList.id; + var many2ones = {}; + var r; + _.each(result[name], function (value) { + if (_.isArray(value)) { + // value is a command + if (value[0] === 0) { + // CREATE + r = self._makeDataPoint({ + modelName: x2manyList.model, + context: x2manyList.context, + fieldsInfo: fieldsInfo, + fields: fields, + viewType: viewType, + }); + x2manyList._changes.push(r.id); + r._changes = value[2]; + + // this is necessary so the fields are initialized + for (var fieldName in value[2]) { + r.data[fieldName] = null; + } + + for (var name in r._changes) { + if (r.fields[name].type === 'many2one') { + var rec = self._makeDataPoint({ + context: r.context, + modelName: r.fields[name].relation, + data: {id: r._changes[name]} + }); + r._changes[name] = rec.id; + many2ones[name] = true; + } + } + } + if (value[0] === 6) { + // REPLACE_WITH + x2manyList._changes = []; + } + } else { + // value is an id + r = self._makeDataPoint({ + modelName: x2manyList.model, + context: x2manyList.context, + fieldsInfo: fieldsInfo, + fields: fields, + res_id: value, + viewType: viewType, + }); + if (!x2manyList._changes) { + x2manyList._changes = []; + } + x2manyList._changes.push(r.id); + } + }); + + // fetch many2ones display_name + _.each(_.keys(many2ones), function (name) { + defs.push(self._fetchNameGets(x2manyList, name)); + }); + } else if (field.type === 'date') { + // process date: convert into a moment instance + record._changes[name] = fieldUtils.parse.date(result[name]); + } else if (field.type === 'datetime') { + // process datetime: convert into a moment instance + record._changes[name] = fieldUtils.parse.datetime(result[name]); + } else { + record._changes[name] = result[name]; + } + }); + return $.when.apply($, defs) + .then(function () { + var shouldApplyOnchange = false; + var field; + for (var field_name in record.data) { + field = record.fields[field_name]; + if (field.onChange) { + shouldApplyOnchange = true; + } + } + if (shouldApplyOnchange) { + return self._applyOnChange(record, fields_key); + } else { + return $.when(); + } + }) + .then(function () { + return self._fetchRelationalData(record); + }) + .then(function () { + return self._postprocess(record); + }) + .then(function () { + return record.id; + }); + }); + }, /** * parse the server values to javascript framwork * @@ -2070,7 +2103,7 @@ var BasicModel = AbstractModel.extend({ * quite a lot of computations to determine what needs to be fetched. This * method is doing that. * - * @see _fetchRecord @see makeDefaultRecord + * @see _fetchRecord @see _makeDefaultRecord * * @param {any} record * @returns {Deferred -> Object} resolves to the finished resource @@ -2078,9 +2111,9 @@ var BasicModel = AbstractModel.extend({ _postprocess: function (record) { var self = this; var defs = []; - _.each(record.fieldNames, function (name) { + _.each(record.getFieldNames(), function (name) { var field = record.fields[name]; - var fieldInfo = record.fieldsInfo[name] || {}; + var fieldInfo = record.fieldsInfo[record.viewType][name] || {}; var options = fieldInfo.options || {}; if (options.always_reload) { if (record.fields[name].type === 'many2one' && record.data[name]) { @@ -2114,7 +2147,7 @@ var BasicModel = AbstractModel.extend({ */ _readGroup: function (list) { var self = this; - var fields = _.uniq(list.fieldNames.concat(list.groupedBy)); + var fields = _.uniq(list.getFieldNames().concat(list.groupedBy)); return this._rpc({ model: list.model, method: 'read_group', @@ -2146,7 +2179,6 @@ var BasicModel = AbstractModel.extend({ domain: group.__domain, context: list.context, fields: list.fields, - fieldNames: list.fieldNames, fieldsInfo: list.fieldsInfo, value: group[rawGroupBy], aggregateValues: aggregateValues, @@ -2155,6 +2187,7 @@ var BasicModel = AbstractModel.extend({ limit: list.limit, openGroupByDefault: list.openGroupByDefault, type: 'list', + viewType: list.viewType, }); list.data.push(newGroup.id); list.count += newGroup.count; @@ -2201,6 +2234,7 @@ var BasicModel = AbstractModel.extend({ var ids = []; var missingIds = []; var upper_bound = list.limit ? Math.min(list.offset + list.limit, list.count) : list.count; + var fieldNames = list.getFieldNames(); for (var i = list.offset; i < upper_bound; i++) { var id = list.res_ids[i]; ids.push(id); @@ -2212,7 +2246,7 @@ var BasicModel = AbstractModel.extend({ def = this._rpc({ model: list.model, method: 'read', - args: [missingIds, list.fieldNames], + args: [missingIds, fieldNames], context: {} // FIXME }); } else { @@ -2228,14 +2262,14 @@ var BasicModel = AbstractModel.extend({ dataPoint = self._makeDataPoint({ data: _.findWhere(records, {id: id}), fieldsInfo: list.fieldsInfo, - fieldNames: list.fieldNames, fields: list.fields, modelName: list.model, parentID: list.id, + viewType: list.viewType, }); // add many2one records - self._parseServerData(list.fieldNames, dataPoint.fields, dataPoint.data); + self._parseServerData(fieldNames, dataPoint.fields, dataPoint.data); list._cache[id] = dataPoint.id; } list.data.push(dataPoint.id); @@ -2272,10 +2306,11 @@ var BasicModel = AbstractModel.extend({ */ _searchReadUngroupedList: function (list) { var self = this; + var fieldNames = list.getFieldNames(); return this._rpc({ route: '/web/dataset/search_read', model: list.model, - fields: list.fieldNames, + fields: fieldNames, domain: list.domain || [], limit: list.limit, offset: list.offset, @@ -2287,14 +2322,14 @@ var BasicModel = AbstractModel.extend({ var dataPoint = self._makeDataPoint({ data: record, fields: list.fields, - fieldNames: list.fieldNames, fieldsInfo: list.fieldsInfo, modelName: list.model, parentID: list.id, + viewType: list.viewType, }); // add many2one records - self._parseServerData(list.fieldNames, dataPoint.fields, dataPoint.data); + self._parseServerData(fieldNames, dataPoint.fields, dataPoint.data); return dataPoint.id; }); list.data = data; diff --git a/addons/web/static/src/js/views/basic/basic_renderer.js b/addons/web/static/src/js/views/basic/basic_renderer.js index d8ddf02d169..2aa4479648b 100644 --- a/addons/web/static/src/js/views/basic/basic_renderer.js +++ b/addons/web/static/src/js/views/basic/basic_renderer.js @@ -64,7 +64,7 @@ return AbstractRenderer.extend({ */ _getFieldValues: function (record) { var result = {}; - _.each(record.fieldNames, function (name) { + _.each(record.getFieldNames(), function (name) { var field = record.fields[name]; var value = record.data[name]; result[name] = value; 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 5b8a3a7d0c9..46ecb5d7e38 100644 --- a/addons/web/static/src/js/views/basic/basic_view.js +++ b/addons/web/static/src/js/views/basic/basic_view.js @@ -40,9 +40,9 @@ var BasicView = AbstractView.extend({ 'action_buttons' in params ? params.action_buttons : true; 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.loadParams.viewType = this.viewType; this.recordID = params.recordID; }, @@ -60,9 +60,14 @@ var BasicView = AbstractView.extend({ _loadData: function () { if (this.recordID) { var record = this.model.get(this.recordID); - var fieldNames = _.difference(record.fieldNames, Object.keys(record.data)); + var viewType = this.viewType; + var viewFields = Object.keys(record.fieldsInfo[viewType]); + var fieldNames = _.difference(viewFields, Object.keys(record.data)); if (fieldNames.length && !this.model.isNew(record.id)) { - return this.model.reload(this.recordID, {fieldNames: fieldNames}); + return this.model.reload(this.recordID, { + fieldNames: fieldNames, + viewType: viewType, + }); } else { return $.when(this.recordID); } 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 3b317b761cf..9c69231937d 100644 --- a/addons/web/static/src/js/views/form/form_controller.js +++ b/addons/web/static/src/js/views/form/form_controller.js @@ -92,17 +92,19 @@ var FormController = BasicController.extend({ createRecord: function () { var self = this; var record = this.model.get(this.handle, {raw: true}); - return this.model.makeDefaultRecord(this.modelName, { - context: this.context, - fieldsInfo: record.fieldsInfo, - fieldNames: record.fieldNames, - fields: record.fields, - res_ids: record.res_ids, - }).then(function (handle) { - self.handle = handle; - self._updateEnv(); - self._toEditMode(); - }); + return this.model.load({ + context: this.context, + fields: record.fields, + fieldsInfo: record.fieldsInfo, + modelName: this.modelName, + res_ids: record.res_ids, + type: 'record', + viewType: 'form', + }).then(function (handle) { + self.handle = handle; + self._updateEnv(); + self._toEditMode(); + }); }, /** * Returns the current res_id, wrapped in a list. This is only used by the @@ -512,32 +514,25 @@ var FormController = BasicController.extend({ * shouldn't be saved in DB when the user clicks on 'Save' in the dialog, * but later on when he clicks on 'Save' in the main form view. For this to * work correctly, the main model and the local id of the opened record must - * be given to the dialog, which will update the list of fields of the - * record with the one of the form view, and this list of fields will be - * reset when the related record is saved (or when the dialog is closed). + * be given to the dialog, which will complete the viewInfo of the record + * with the one of the form view. * * @private * @param {OdooEvent} event */ _onOpenOne2ManyRecord: function (event) { - var self = this; var data = event.data; var record; if (data.id) { record = this.model.get(data.id, {raw: true}); } - // reset the list of fields when the user clicks on 'Save' in the modal - var on_saved = function (record) { - self.model.setFieldProps(record.id, data.viewInfo); - data.on_saved(record); - }; - var dialog = new dialogs.FormViewDialog(this, { + new dialogs.FormViewDialog(this, { context: data.context, domain: data.domain, fields_view: data.fields_view, model: this.model, - on_saved: on_saved, + on_saved: event.data.on_saved, parentID: this.handle, readonly: data.readonly, recordID: record && record.id, @@ -546,16 +541,6 @@ var FormController = BasicController.extend({ shouldSaveLocally: true, title: (record ? _t("Open:") : _t("Create")) + data.field.string, }).open(); - - // reset the list of fields when the user closes the modal (this is - // necessary if he didn't save, but this is not sufficient if he saved - // because in this case, a (re-)rendering of the related record is - // done in the kanban/list o2m field before the dialog is closed) - dialog.on('closed', this, function () { - if (record) { - this.model.setFieldProps(record.id, data.viewInfo); - } - }); }, /** * Open an existing record in a form view dialog 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 751923c3b9f..2cdc2f91a1d 100644 --- a/addons/web/static/src/js/views/form/form_renderer.js +++ b/addons/web/static/src/js/views/form/form_renderer.js @@ -301,8 +301,9 @@ return BasicRenderer.extend({ mode: is_readonly ? 'readonly' : this.mode, required: is_required, idForLabel: this._getIDForLabel(name), + viewType: 'form', }; - var attrs = this.state.fieldsInfo[name]; + var attrs = this.state.fieldsInfo.form[name]; var widget = new attrs.Widget(this, name, this.state, options); this.widgets.push(widget); var def = widget.__widgetRenderAndInsert(function () {}); 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 423ed08085c..2edbc23b5b5 100644 --- a/addons/web/static/src/js/views/form/form_view.js +++ b/addons/web/static/src/js/views/form/form_view.js @@ -18,7 +18,7 @@ var FormView = BasicView.extend({ icon: 'fa-edit', multi_record: false, searchable: false, - + viewType: 'form', /** * @override */ @@ -55,7 +55,7 @@ var FormView = BasicView.extend({ var defs = []; var fields = this.loadParams.fields; - _.each(self.loadParams.fieldsInfo, function (attrs, fieldName) { + _.each(self.loadParams.fieldsInfo.form, function (attrs, fieldName) { var field = fields[fieldName]; if (field.type !== 'one2many' && field.type !== 'many2many') { return; diff --git a/addons/web/static/src/js/views/kanban/kanban_column.js b/addons/web/static/src/js/views/kanban/kanban_column.js index 66fb538872c..0ba08d08b1a 100644 --- a/addons/web/static/src/js/views/kanban/kanban_column.js +++ b/addons/web/static/src/js/views/kanban/kanban_column.js @@ -46,7 +46,7 @@ var KanbanColumn = Widget.extend({ // todo: handle group_by_m2o (nameget) this.title = value || _t('Undefined'); this.folded = !data.isOpen; - this.has_active_field = _.contains(data.fieldNames, 'active'); + this.has_active_field = 'active' in data.fields; this.size = data.count; this.values = data.values; this.fields = data.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 7dc2f0cbfa3..1e0fe55736c 100644 --- a/addons/web/static/src/js/views/kanban/kanban_model.js +++ b/addons/web/static/src/js/views/kanban/kanban_model.js @@ -27,8 +27,8 @@ var KanbanModel = BasicModel.extend({ res_id: resId, modelName: group.model, fields: group.fields, - fieldNames: group.fieldNames, fieldsInfo: group.fieldsInfo, + viewType: group.viewType, }); group.data.unshift(new_record); group.count++; @@ -65,13 +65,14 @@ var KanbanModel = BasicModel.extend({ context: parent.context, domain: parent.domain.concat([[groupBy,"=",result[0]]]), fields: parent.fields, - fieldNames: parent.fieldNames, + fieldsInfo: parent.fieldsInfo, groupedBy: parent.groupedBy, isOpen: true, limit: parent.limit, openGroupByDefault: true, orderedBy: parent.orderedBy, value: result, + viewType: parent.viewType, }); // newGroup.is_open = true; 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 128dda9413f..e9695a67e2d 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.fieldsInfo = state.fieldsInfo; + this.fieldsInfo = state.fieldsInfo.kanban; this.modelName = state.model; this.options = options; @@ -359,7 +359,7 @@ var KanbanRecord = Widget.extend({ _transformRecord: function (recordData) { var self = this; var new_record = {}; - _.each(this.state.fieldNames, function (name) { + _.each(this.state.getFieldNames(), function (name) { var value = recordData[name]; var r = _.clone(self.fields[name] || {}); diff --git a/addons/web/static/src/js/views/kanban/kanban_renderer.js b/addons/web/static/src/js/views/kanban/kanban_renderer.js index dd6c193701b..4fe030eb168 100644 --- a/addons/web/static/src/js/views/kanban/kanban_renderer.js +++ b/addons/web/static/src/js/views/kanban/kanban_renderer.js @@ -94,7 +94,10 @@ var KanbanRenderer = BasicRenderer.extend({ transformQwebTemplate(templates, state.fields); this.qweb.add_template(utils.json_node_to_xml(templates)); - this.recordOptions = _.extend({}, params.record_options, { qweb: this.qweb }); + this.recordOptions = _.extend({}, params.record_options, { + qweb: this.qweb, + viewType: 'kanban', + }); this.columnOptions = _.extend({}, params.column_options, { qweb: this.qweb }); }, 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 0cfa6d24a20..4716e7a3bc3 100644 --- a/addons/web/static/src/js/views/kanban/kanban_view.js +++ b/addons/web/static/src/js/views/kanban/kanban_view.js @@ -20,6 +20,7 @@ var KanbanView = BasicView.extend({ Controller: KanbanController, Renderer: KanbanRenderer, }, + viewType: 'kanban', init: function (viewInfo, params) { this._super.apply(this, arguments); 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 d8ab57e7a69..2460e52cfea 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.fieldsInfo[node.attrs.name].Widget; + var Widget = this.state.fieldsInfo.list[node.attrs.name].Widget; if (Widget && Widget.prototype.readonly) { return true; } @@ -268,8 +268,10 @@ ListRenderer.include({ return; } var $td = this._findTd($row, colIndex); - var widget = new this.state.fieldsInfo[name].Widget(this, name, record, { - mode: 'edit' + var Widget = this.state.fieldsInfo.list[name].Widget; + var widget = new Widget(this, name, record, { + mode: 'edit', + viewType: 'list', }); if (widget.replace_element) { $td.empty(); 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 3495e78d2f2..ad364951815 100644 --- a/addons/web/static/src/js/views/list/list_renderer.js +++ b/addons/web/static/src/js/views/list/list_renderer.js @@ -284,8 +284,10 @@ var ListRenderer = BasicRenderer.extend({ } var name = node.attrs.name; if (node.attrs.widget) { - var widget = new this.state.fieldsInfo[name].Widget(this, name, record, { + var Widget = this.state.fieldsInfo.list[name].Widget; + var widget = new Widget(this, name, record, { mode: 'readonly', + viewType: 'list', }); widget.appendTo($td); $td.addClass('o_' + node.attrs.widget + '_cell'); @@ -504,7 +506,7 @@ var ListRenderer = BasicRenderer.extend({ } var description; if (node.attrs.widget) { - description = this.state.fieldsInfo[name].Widget.prototype.description; + description = this.state.fieldsInfo.list[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 5661d3cf4f8..1260a8bc69d 100644 --- a/addons/web/static/src/js/views/list/list_view.js +++ b/addons/web/static/src/js/views/list/list_view.js @@ -24,6 +24,7 @@ var ListView = BasicView.extend({ Renderer: ListRenderer, Controller: ListController, }), + viewType: 'list', /** * @override * diff --git a/addons/web/static/src/js/views/view_dialogs.js b/addons/web/static/src/js/views/view_dialogs.js index 7482426e067..828ecf58e90 100644 --- a/addons/web/static/src/js/views/view_dialogs.js +++ b/addons/web/static/src/js/views/view_dialogs.js @@ -160,7 +160,7 @@ var FormViewDialog = ViewDialog.extend({ fields_view_def.then(function (viewInfo) { if (self.recordID) { - self.model.setFieldProps(self.recordID, viewInfo); + self.model.addFieldsInfo(self.recordID, viewInfo); } var formview = new FormView(viewInfo, { modelName: self.res_model, diff --git a/addons/web/static/test/fields/relational_fields_tests.js b/addons/web/static/test/fields/relational_fields_tests.js index 9b562b1a3a9..482c2f4e104 100644 --- a/addons/web/static/test/fields/relational_fields_tests.js +++ b/addons/web/static/test/fields/relational_fields_tests.js @@ -949,6 +949,8 @@ QUnit.module('relational_fields', { '' + '
' + '' + + // foo will be in the form but not in the kanban + '' + '' + '' + '', diff --git a/addons/web/static/test/helpers/test_utils.js b/addons/web/static/test/helpers/test_utils.js index 90eb204482a..442c67b1954 100644 --- a/addons/web/static/test/helpers/test_utils.js +++ b/addons/web/static/test/helpers/test_utils.js @@ -335,19 +335,12 @@ function addMockEnvironment(widget, params) { * @param {Object} params This object will be given to addMockEnvironment, so * any parameters from that method applies * @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.fieldsInfo] map of field names to field attributes * @returns {Model} */ function createModel(params) { var widget = new Widget(); - var model = new params.Model(widget, { - fieldsInfo: params.fieldsInfo, - fieldNames: params.fieldNames, - fields: params.fields, - }); + var model = new params.Model(widget); addMockEnvironment(widget, params); diff --git a/addons/web/static/test/views/basic_model_tests.js b/addons/web/static/test/views/basic_model_tests.js index b0285380b9c..cda5e044e08 100644 --- a/addons/web/static/test/views/basic_model_tests.js +++ b/addons/web/static/test/views/basic_model_tests.js @@ -309,11 +309,15 @@ QUnit.module('Views', { assert.expect(3); this.params.res_id = 1; - this.params.fieldNames = ['category']; - this.params.fieldsInfo = {category: { - relatedFields: {display_name: {type: "char"}}, - fieldsInfo: {display_name: {}}, - }}; + this.params.fieldsInfo = { + default: { + category: { + fieldsInfo: {default: {display_name: {}}}, + relatedFields: {display_name: {type: "char"}}, + viewType: 'default', + }, + }, + }; var model = createModel({ Model: BasicModel, @@ -336,11 +340,15 @@ QUnit.module('Views', { QUnit.test('can use command add and get many2many value with date field', function (assert) { assert.expect(2); - this.params.fieldNames = ['category']; - this.params.fieldsInfo = {category: { - relatedFields: {date: {type: "date"}}, - fieldsInfo: {date: {}}, - }}; + this.params.fieldsInfo = { + default: { + category: { + fieldsInfo: {default: {date: {}}}, + relatedFields: {date: {type: "date"}}, + viewType: 'default', + }, + }, + }; var model = createModel({ Model: BasicModel, @@ -424,16 +432,17 @@ QUnit.module('Views', { QUnit.test('can make a default_record, no onchange', function (assert) { assert.expect(3); + this.params.context = {}; this.params.fieldNames = ['product_id', 'category', 'product_ids']; this.params.res_id = undefined; - this.params.context = {}; + this.params.type = 'record'; var model = createModel({ Model: BasicModel, data: this.data, }); - model.makeDefaultRecord('partner', this.params).then(function (resultID) { + model.load(this.params).then(function (resultID) { var record = model.get(resultID); assert.strictEqual(record.data.product_id, false, "m2o default value should be false"); assert.deepEqual(record.data.product_ids.data, [], "o2m default should be []"); @@ -449,9 +458,10 @@ QUnit.module('Views', { // of 'false', which does not work with the mockserver mockOnChange method. this.data.partner.onchanges.product_id = true; + this.params.context = {}; this.params.fieldNames = ['product_id']; this.params.res_id = undefined; - this.params.context = {}; + this.params.type = 'record'; var model = createModel({ Model: BasicModel, @@ -464,7 +474,7 @@ QUnit.module('Views', { }, }); - model.makeDefaultRecord('partner', this.params).then(function (resultID) { + model.load(this.params).then(function (resultID) { var record = model.get(resultID); assert.strictEqual(record.data.product_id, false, "m2o default value should be false"); }); @@ -493,9 +503,11 @@ QUnit.module('Views', { context: {}, fieldNames: ['other_product_id', 'product_id'], fields: fields, + modelName: 'partner', + type: 'record', }; - model.makeDefaultRecord('partner', params).then(function (resultID) { + model.load(params).then(function (resultID) { var record = model.get(resultID); assert.strictEqual(record.data.product_id.data.display_name, "xphone", "should have fetched correct name"); @@ -675,6 +687,8 @@ QUnit.module('Views', { assert.expect(4); this.params.fieldNames = ['product_ids']; + this.params.res_id = undefined; + this.params.type = 'record'; var id; var model = createModel({ @@ -696,7 +710,7 @@ QUnit.module('Views', { }, }); - model.makeDefaultRecord('partner', this.params).then(function (resultID) { + model.load(this.params).then(function (resultID) { var record = model.get(resultID); model.save(record.id, {reload: false}); record = model.get(resultID); @@ -759,6 +773,8 @@ QUnit.module('Views', { }; this.params.fieldNames = ['total', 'product_ids']; + this.params.res_id = undefined; + this.params.type = 'record'; var model = createModel({ Model: BasicModel, @@ -772,7 +788,7 @@ QUnit.module('Views', { }, }); - model.makeDefaultRecord('partner', this.params).then(function (resultID) { + model.load(this.params).then(function (resultID) { var record = model.get(resultID); assert.strictEqual(record.data.product_ids.data.length, 0, @@ -868,9 +884,10 @@ QUnit.module('Views', { fieldNames: ['date'], fields: this.data.partner.fields, modelName: 'partner', + type: 'record', }; - model.makeDefaultRecord('partner', params).then(function (resultID) { + model.load(params).then(function (resultID) { var record = model.get(resultID); assert.strictEqual(record.data.date, false, "date default value should be false"); }); @@ -891,9 +908,10 @@ QUnit.module('Views', { fieldNames: ['category'], fields: this.data.partner.fields, modelName: 'partner', + type: 'record', }; - model.makeDefaultRecord('partner', params).then(function (resultID) { + model.load(params).then(function (resultID) { var record = model.get(resultID); assert.ok(_.isEqual(record.data.category.res_ids, [12, 14]), "category field should have correct default value"); @@ -943,10 +961,13 @@ QUnit.module('Views', { }); this.params.fieldsInfo = { - product_id: { - context: "{'hello2': 'world', 'test2': foo}", - domain: "[['hello2', 'like', 'world'], ['test2', 'like', foo]]", - }, + default: { + foo: {}, + product_id: { + context: "{'hello2': 'world', 'test2': foo}", + domain: "[['hello2', 'like', 'world'], ['test2', 'like', foo]]", + }, + } }; model.load(this.params).then(function (resultID) {