From 905e01921f3c3ef43ace6fc537d1d8c0d280002c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Sun, 5 Mar 2017 10:05:56 +0100 Subject: [REF] web, *: redesign all JS views This commit introduce a full redesign of all JS views. We started basically from scratch. The goal was to unify all the various views under a common framework, to make them testable, to make then usable in different conditions (in studio, or in the frontend), and to make our lives easier. Some important points are: - we introduced new coding guidelines (camelCase, 80 chars width, ...) - we have a brand new testing framework (still QUnit based) - kanban view moved to the web addon - calendar view (formerly web_calender) moved to web as well - the tree view was removed - all new code should be documented We hope that this code is the start of a new era for the Odoo web client, we want to have a high quality codebase, well documented, well tested, well designed. Work done by the framework team: mostly aab, ged, chm, dmo, qsm --- addons/web/static/src/js/views/basic/basic_view.js | 171 +++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 addons/web/static/src/js/views/basic/basic_view.js (limited to 'addons/web/static/src/js/views/basic/basic_view.js') diff --git a/addons/web/static/src/js/views/basic/basic_view.js b/addons/web/static/src/js/views/basic/basic_view.js new file mode 100644 index 00000000000..1601cf4c350 --- /dev/null +++ b/addons/web/static/src/js/views/basic/basic_view.js @@ -0,0 +1,171 @@ +odoo.define('web.BasicView', function (require) { +"use strict"; + +/** + * The BasicView is an abstract class designed to share code between views that + * want to use a basicModel. As of now, it is the form view, the list view and + * the kanban view. + * + * The main focus of this class is to process the arch and extract field + * attributes, as well as some other useful informations. + */ + +var AbstractView = require('web.AbstractView'); +var BasicController = require('web.BasicController'); +var BasicModel = require('web.BasicModel'); +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, { + Model: BasicModel, + Controller: BasicController, + }), + /** + * process the fields_view to find all fields appearing in the views. + * list those fields' name in this.fields_name, which will be the list + * of fields read when data is fetched. + * this.fields is the list of all field's description (the result of + * the fields_get), where the fields appearing in the fields_view are + * augmented with their attrs and some flags if they require a + * particular handling. + * + * @param {Object} arch + * @param {Object} fields + * @param {Object} params + */ + init: function (arch, fields, params) { + this._super.apply(this, arguments); + + var result = this._processFieldsView(arch, fields); + var fieldAttrs = result.fieldAttrs; + var view_fields = result.view_fields; + var fieldNames = _.keys(view_fields); + fields = _.defaults(view_fields, 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 field_name in fields) { + var field = fields[field_name]; + _.each(field.views, function (inner_fields_view) { + result = self._processFieldsView(inner_fields_view.arch, inner_fields_view.fields); + inner_fields_view.fieldAttrs = result.fieldAttrs; + inner_fields_view.fields = result.view_fields; + }); + if (field.type === 'one2many' || field.type === 'many2many') { + if (field.views && field.views.tree) { + field.relatedFields = field.views.tree.fields; + field.fieldAttrs = field.views.tree.fieldAttrs; + field.limit = 80; + } else if (field.views && field.views.kanban) { + field.relatedFields = field.views.kanban.fields; + field.fieldAttrs = field.views.kanban.fieldAttrs; + field.limit = 40; + } else { + 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.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; + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * 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 view_fields = 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 || {}; + var field = _.extend({}, fields[node.attrs.name]); + self._processField(field, node, attrs); + view_fields[node.attrs.name] = field; + fieldAttrs[node.attrs.name] = attrs; + return false; + } + return node.tag !== 'arch'; + }); + return { + view_fields: view_fields, + fieldAttrs: fieldAttrs, + }; + }, + /** + * Process a field node, in particular, put a flag on the field to give + * special directives to the BasicModel. + * + * @param {Object} field + * @param {Object} node + * @param {Object} attrs + */ + _processField: function (field, node, attrs) { + if (attrs.options) { + var options = pyeval.py_eval(attrs.options || '{}'); + if (options.always_reload) { + field.__always_reload = true; + } + } + if (field.type === 'many2one') { + if (node.attrs.widget === 'statusbar' || node.attrs.widget === 'radio') { + field.__fetch_status = true; + } else if (node.attrs.widget === 'selection') { + field.__fetch_selection = true; + } + } + if (node.attrs.widget === 'many2many_checkboxes') { + field.__fetch_many2manys = true; + } + if (node.attrs.on_change) { + field.onChange = "1"; + } + }, +}); + +return BasicView; + +}); -- cgit v1.2.3 From 7c41254d8f5474deb755fbd5ff9ce6f40576fa5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Fri, 24 Feb 2017 11:06:06 +0100 Subject: [REF] web: small linting pass, add some comments, update style --- addons/web/static/src/js/views/basic/basic_view.js | 24 +++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'addons/web/static/src/js/views/basic/basic_view.js') 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 1601cf4c350..947a3ad26b6 100644 --- a/addons/web/static/src/js/views/basic/basic_view.js +++ b/addons/web/static/src/js/views/basic/basic_view.js @@ -53,9 +53,9 @@ var BasicView = AbstractView.extend({ var result = this._processFieldsView(arch, fields); var fieldAttrs = result.fieldAttrs; - var view_fields = result.view_fields; - var fieldNames = _.keys(view_fields); - fields = _.defaults(view_fields, fields); + 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. @@ -63,12 +63,12 @@ var BasicView = AbstractView.extend({ // 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 field_name in fields) { - var field = fields[field_name]; - _.each(field.views, function (inner_fields_view) { - result = self._processFieldsView(inner_fields_view.arch, inner_fields_view.fields); - inner_fields_view.fieldAttrs = result.fieldAttrs; - inner_fields_view.fields = result.view_fields; + 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') { if (field.views && field.views.tree) { @@ -114,7 +114,7 @@ var BasicView = AbstractView.extend({ */ _processFieldsView: function (arch, fields) { var self = this; - var view_fields = Object.create(null); + var viewFields = Object.create(null); var fieldAttrs = Object.create(null); traverse(arch, function (node) { if (typeof node === 'string') { @@ -124,14 +124,14 @@ var BasicView = AbstractView.extend({ var attrs = node.attrs || {}; var field = _.extend({}, fields[node.attrs.name]); self._processField(field, node, attrs); - view_fields[node.attrs.name] = field; + viewFields[node.attrs.name] = field; fieldAttrs[node.attrs.name] = attrs; return false; } return node.tag !== 'arch'; }); return { - view_fields: view_fields, + view_fields: viewFields, fieldAttrs: fieldAttrs, }; }, -- cgit v1.2.3 From 206453c83689ad0dc6ceec3686153ab79bf88660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Mon, 27 Feb 2017 19:39:41 +0100 Subject: [FIX] web: various fixes with one2many/formviewdialogs We share the basic model record between views, and we have to make sure that everything is properly coordinated. The main issue solved by this commit is the fact that we can now edit a one2many record, then close the dialog, then edit it again and finally cancel the changes, which bring us to the previous step. Also, we had to slightly improve the AbstractView API --- addons/web/static/src/js/views/basic/basic_view.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'addons/web/static/src/js/views/basic/basic_view.js') 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 947a3ad26b6..aea0b88dcf3 100644 --- a/addons/web/static/src/js/views/basic/basic_view.js +++ b/addons/web/static/src/js/views/basic/basic_view.js @@ -99,12 +99,26 @@ var BasicView = AbstractView.extend({ this.loadParams.fieldNames = fieldNames; this.loadParams.fields = fields; this.loadParams.limit = parseInt(arch.attrs.limit, 10) || params.limit; + this.recordID = params.recordID; }, //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- + /** + * In some cases, we already have a preloaded record + * + * @override + * @private + * @returns {Deferred} + */ + _loadData: function () { + if (this.recordID) { + return $.when(this.recordID); + } + return this._super.apply(this, arguments); + }, /** * Visit all nodes in the arch field and process each fields * -- cgit v1.2.3 From f0cfe7c174307009575661d8ebe4c2e5d84c1768 Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Thu, 9 Mar 2017 12:44:40 +0100 Subject: [FIX] web: load missing fields when opening form view dialogs Problem was that sub form field has usually more fields than the list view in one2many, so we have to load missing fields --- addons/web/static/src/js/views/basic/basic_view.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'addons/web/static/src/js/views/basic/basic_view.js') 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 aea0b88dcf3..d8e41b99835 100644 --- a/addons/web/static/src/js/views/basic/basic_view.js +++ b/addons/web/static/src/js/views/basic/basic_view.js @@ -115,7 +115,13 @@ var BasicView = AbstractView.extend({ */ _loadData: function () { if (this.recordID) { - return $.when(this.recordID); + var record = this.model.get(this.recordID); + var fieldNames = _.difference(record.fieldNames, Object.keys(record.data)); + if (fieldNames.length && !this.model.isNew(record.id)) { + return this.model.reload(this.recordID, {fieldNames: fieldNames}); + } else { + return $.when(this.recordID); + } } return this._super.apply(this, arguments); }, -- cgit v1.2.3 From 228e18e81ae2439c307825cd8127e800dfec571c Mon Sep 17 00:00:00 2001 From: qsm-odoo Date: Wed, 8 Mar 2017 16:03:28 +0100 Subject: [REF] web, mail: include evaled field options in datapoint Field options do not depend on any context/data. They can so be "computed" once and for all in the BasicView implementation instead of in the AbstractField's one. Also, remove the flag __always_reload and leave it be in the options object (always_reload). --- addons/web/static/src/js/views/basic/basic_view.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'addons/web/static/src/js/views/basic/basic_view.js') 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 d8e41b99835..d493bcb9729 100644 --- a/addons/web/static/src/js/views/basic/basic_view.js +++ b/addons/web/static/src/js/views/basic/basic_view.js @@ -164,12 +164,10 @@ var BasicView = AbstractView.extend({ * @param {Object} attrs */ _processField: function (field, node, attrs) { - if (attrs.options) { - var options = pyeval.py_eval(attrs.options || '{}'); - if (options.always_reload) { - field.__always_reload = true; - } + 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 (field.type === 'many2one') { if (node.attrs.widget === 'statusbar' || node.attrs.widget === 'radio') { field.__fetch_status = true; -- cgit v1.2.3 From 49f7a9f657b011080666475f836982665615dc7c Mon Sep 17 00:00:00 2001 From: qsm-odoo Date: Thu, 9 Mar 2017 15:12:58 +0100 Subject: [REF] web, mail: use field registry in views, not renderers Before this commit, the AbstractField specialization to use to instantiate a field was determined in the renderers. Now this is regrouped in BasicView. Note: renderers are still left free to not use the determined Class (like Kanban and not editable List which instantiate widgets only if the "widget" attribute is present). --- addons/web/static/src/js/views/basic/basic_view.js | 46 ++++++++++++++++------ 1 file changed, 35 insertions(+), 11 deletions(-) (limited to 'addons/web/static/src/js/views/basic/basic_view.js') 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 d493bcb9729..8fbff881bd1 100644 --- a/addons/web/static/src/js/views/basic/basic_view.js +++ b/addons/web/static/src/js/views/basic/basic_view.js @@ -13,6 +13,7 @@ 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'); /** @@ -141,9 +142,9 @@ var BasicView = AbstractView.extend({ return false; } if (node.tag === 'field') { - var attrs = node.attrs || {}; - var field = _.extend({}, fields[node.attrs.name]); - self._processField(field, node, attrs); + 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; @@ -159,29 +160,52 @@ var BasicView = AbstractView.extend({ * Process a field node, in particular, put a flag on the field to give * special directives to the BasicModel. * - * @param {Object} field - * @param {Object} node - * @param {Object} attrs + * @param {Object} field - the field properties + * @param {Object} attrs - the field attributes (from the xml) */ - _processField: function (field, node, attrs) { + _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 (field.type === 'many2one') { - if (node.attrs.widget === 'statusbar' || node.attrs.widget === 'radio') { + if (attrs.widget === 'statusbar' || attrs.widget === 'radio') { field.__fetch_status = true; - } else if (node.attrs.widget === 'selection') { + } else if (attrs.widget === 'selection') { field.__fetch_selection = true; } } - if (node.attrs.widget === 'many2many_checkboxes') { + if (attrs.widget === 'many2many_checkboxes') { field.__fetch_many2manys = true; } - if (node.attrs.on_change) { + 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; -- cgit v1.2.3 From b5b649e5b21d41279f12f0dbe6cfa53ca2c52580 Mon Sep 17 00:00:00 2001 From: qsm-odoo Date: Fri, 10 Mar 2017 12:28:26 +0100 Subject: [FIX] web: fetching special data in field widgets Some field widgets need additional data to render themself. These data should ideally be fetched in the data fetching phase. In 10.0, these data were fetched during field rendering which implied that the data fetching was done sequentially. To limit this effect, before this commit, these special fields were flagged by the basic view according to some field properties and the basic model flagged the fields with the required data. Now, the system has been improved. The field widget classes can define a key "specialData" which refers to a BasicModel method. These methods will be used to fetch the required data at the right time and these data will be available at `this.record.specialData[this.name]` in the AbstractField instance. The new system also fixes a bug: the required data was fetched only at record loading but should in fact be re-fetched at each view change. --- addons/web/static/src/js/views/basic/basic_view.js | 10 ---------- 1 file changed, 10 deletions(-) (limited to 'addons/web/static/src/js/views/basic/basic_view.js') 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 8fbff881bd1..939d7dabee9 100644 --- a/addons/web/static/src/js/views/basic/basic_view.js +++ b/addons/web/static/src/js/views/basic/basic_view.js @@ -170,16 +170,6 @@ var BasicView = AbstractView.extend({ attrs.options = attrs.options ? pyeval.py_eval(attrs.options) : {}; } - if (field.type === 'many2one') { - if (attrs.widget === 'statusbar' || attrs.widget === 'radio') { - field.__fetch_status = true; - } else if (attrs.widget === 'selection') { - field.__fetch_selection = true; - } - } - if (attrs.widget === 'many2many_checkboxes') { - field.__fetch_many2manys = true; - } if (attrs.on_change) { field.onChange = "1"; } -- cgit v1.2.3 From 5e91027a86217e440608ed80f1da5d6f82881e61 Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Tue, 14 Mar 2017 17:34:14 +0100 Subject: [FIX] web: form view must load views for x2many without inline tree arch * Load views before loading data. * Load the view used by the mode (by default use kanban for mobile) * Add attribute useSubview on the widget to load view of the related model --- addons/web/static/src/js/views/basic/basic_view.js | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) (limited to 'addons/web/static/src/js/views/basic/basic_view.js') 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 939d7dabee9..35c0dd5b6f0 100644 --- a/addons/web/static/src/js/views/basic/basic_view.js +++ b/addons/web/static/src/js/views/basic/basic_view.js @@ -72,22 +72,12 @@ var BasicView = AbstractView.extend({ innerFieldsView.fields = result.view_fields; }); if (field.type === 'one2many' || field.type === 'many2many') { - if (field.views && field.views.tree) { - field.relatedFields = field.views.tree.fields; - field.fieldAttrs = field.views.tree.fieldAttrs; - field.limit = 80; - } else if (field.views && field.views.kanban) { - field.relatedFields = field.views.kanban.fields; - field.fieldAttrs = field.views.kanban.fieldAttrs; - field.limit = 40; - } else { - field.relatedFields = { - color: {type: 'int'}, - display_name: {type: 'char'}, - id: {type: 'integer'}, - }; - field.fieldAttrs = {}; - } + field.relatedFields = { + color: {type: 'int'}, + display_name: {type: 'char'}, + id: {type: 'integer'}, + }; + field.fieldAttrs = {}; } } -- cgit v1.2.3 From eaa66d11568b415b8d6b4c7a17a058b5ec1df4a6 Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Tue, 21 Mar 2017 15:30:58 +0100 Subject: [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. --- addons/web/static/src/js/views/basic/basic_view.js | 131 ++------------------- 1 file changed, 7 insertions(+), 124 deletions(-) (limited to 'addons/web/static/src/js/views/basic/basic_view.js') 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; -- cgit v1.2.3 From 3d959382fd92aca6c48ea2413c646edcc1f178f4 Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Fri, 31 Mar 2017 14:10:46 +0200 Subject: [FIX] web,mail: one2many records: multiple views A record of the basic model may need to be displayed in different views (e.g. a o2m record is displayed in the, e.g., Kanban view inside the main form view, and if clicked, it is also displayed in the form view opened in a dialog). However, the fields present in those views may differ. The previous implementation of fieldsInfo in the record didn't handle that, as it was a dict whose keys were field names. With this commit, the fieldsInfo is dict whose keys are view types, and the values are dicts whose keys are field names (the fields of the corresponding views, with they attrs in the view). This allows to create multiple o2m records in a row in a form view. --- addons/web/static/src/js/views/basic/basic_view.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'addons/web/static/src/js/views/basic/basic_view.js') 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); } -- cgit v1.2.3 From d0f321d6cc198045ff796865d94ddd15bd6d0b99 Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Wed, 5 Apr 2017 11:32:56 +0200 Subject: [FIX] web: Form: don't discard onchange when editing a o2m Assume that there is an onchange on a form view that upates the records of a one2many field. Before this rev., if the user edited one of those one2many records (in a dialog) after the onchange, the onchange values were lost. --- addons/web/static/src/js/views/basic/basic_view.js | 1 + 1 file changed, 1 insertion(+) (limited to 'addons/web/static/src/js/views/basic/basic_view.js') 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 46ecb5d7e38..d850b2b7a2f 100644 --- a/addons/web/static/src/js/views/basic/basic_view.js +++ b/addons/web/static/src/js/views/basic/basic_view.js @@ -66,6 +66,7 @@ var BasicView = AbstractView.extend({ if (fieldNames.length && !this.model.isNew(record.id)) { return this.model.reload(this.recordID, { fieldNames: fieldNames, + keepChanges: true, viewType: viewType, }); } else { -- cgit v1.2.3